From b9e5a77fde31f96886704cc9057d296d63fee1de Mon Sep 17 00:00:00 2001 From: Aditya Pawar Date: Sat, 2 Dec 2023 14:06:59 -0800 Subject: [PATCH 1/9] NOOOO --- src/app/(tabs)/search/_layout.tsx | 9 +- src/app/(tabs)/search/index.tsx | 1 + src/app/settings.tsx | 5 +- src/components/FilterModal/FilterModal.tsx | 80 ++++++----- src/utils/FilterContext.tsx | 159 +++++++++++++++++++++ 5 files changed, 214 insertions(+), 40 deletions(-) create mode 100644 src/utils/FilterContext.tsx diff --git a/src/app/(tabs)/search/_layout.tsx b/src/app/(tabs)/search/_layout.tsx index d021a461..28b30f3b 100644 --- a/src/app/(tabs)/search/_layout.tsx +++ b/src/app/(tabs)/search/_layout.tsx @@ -1,10 +1,13 @@ import { Stack } from 'expo-router'; +import { FilterContextProvider } from '../../../utils/FilterContext'; function StackLayout() { return ( - - - + + + + + ); } diff --git a/src/app/(tabs)/search/index.tsx b/src/app/(tabs)/search/index.tsx index 23450af4..f872ce24 100644 --- a/src/app/(tabs)/search/index.tsx +++ b/src/app/(tabs)/search/index.tsx @@ -22,6 +22,7 @@ import { fetchAllStoryPreviews } from '../../../queries/stories'; import { StoryPreview, RecentSearch, Genre } from '../../../queries/types'; import colors from '../../../styles/colors'; import globalStyles from '../../../styles/globalStyles'; +import { useFilter } from '../../../utils/FilterContext'; const getRecentSearch = async () => { try { diff --git a/src/app/settings.tsx b/src/app/settings.tsx index 133c866c..c03a7757 100644 --- a/src/app/settings.tsx +++ b/src/app/settings.tsx @@ -1,8 +1,8 @@ import DateTimePicker from '@react-native-community/datetimepicker'; import { Redirect, router } from 'expo-router'; import { useEffect, useState } from 'react'; -import { Text, StyleSheet, View, Alert, Platform } from 'react-native'; -import { Button, Input } from 'react-native-elements'; +import { Text, StyleSheet, Alert, Platform } from 'react-native'; +import { Button } from 'react-native-elements'; import { SafeAreaView } from 'react-native-safe-area-context'; import StyledButton from '../components/StyledButton/StyledButton'; @@ -13,6 +13,7 @@ import supabase from '../utils/supabase'; function SettingsScreen() { const { session, signOut } = useSession(); + const [loading, setLoading] = useState(true); const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); diff --git a/src/components/FilterModal/FilterModal.tsx b/src/components/FilterModal/FilterModal.tsx index c2ded653..72ba1d8c 100644 --- a/src/components/FilterModal/FilterModal.tsx +++ b/src/components/FilterModal/FilterModal.tsx @@ -1,11 +1,12 @@ import { BottomSheet, CheckBox } from '@rneui/themed'; -import { useState } from 'react'; import { View, Text, ScrollView, Pressable } from 'react-native'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import 'react-native-gesture-handler'; import styles from './styles'; import Icon from '../../../assets/icons'; +import { TagFilter, useFilter } from '../../utils/FilterContext'; +import { useEffect, useRef } from 'react'; type FilterModalProps = { isVisible: boolean; @@ -13,28 +14,33 @@ type FilterModalProps = { title: string; }; +type ParentTagFilter = { children: TagFilter[] } & TagFilter; + function FilterModal({ isVisible, setIsVisible, title }: FilterModalProps) { - const [checked1, toggleChecked1] = useState(false); - const [checked2, toggleChecked2] = useState(false); - const [checked3, toggleChecked3] = useState(false); + const { dispatch, filters } = useFilter(); + const nestedFilters = useRef(new Map()); + + useEffect(() => { + nestFilters(); + // console.log(nestedFilters.current); + }, [filters]); + + const nestFilters = () => { + Array.from(filters) + .filter(([_, { parent }]) => parent === null) + .map(([id, parent]) => + nestedFilters.current.set(id, { + ...parent, + children: [], + } as ParentTagFilter), + ); - const genres = [ - { - title: 'Fiction', - state: checked1, - setState: toggleChecked1, - }, - { - title: 'Erasure & Found Poetry', - state: checked2, - setState: toggleChecked2, - }, - { - title: 'Non-Fiction', - state: checked3, - setState: toggleChecked3, - }, - ]; + Array.from(filters).map(([_, filter]) => { + if (filter.parent) { + nestedFilters.current.get(filter.parent)?.children.push(filter); + } + }); + }; return ( @@ -59,20 +65,24 @@ function FilterModal({ isVisible, setIsVisible, title }: FilterModalProps) { bounces={false} style={styles.scrollView} > - {genres.map(item => { - return ( - item.setState(!item.state)} - iconType="material-community" - checkedIcon="checkbox-marked" - uncheckedIcon="checkbox-blank-outline" - checkedColor="black" - /> - ); - })} + {Array.from(nestedFilters.current).map( + ([id, parentFilter]: [number, ParentTagFilter]) => { + return ( + + dispatch({ type: 'TOGGLE_FILTER', id: parentFilter.id }) + } + iconType="material-community" + checkedIcon="checkbox-marked" + uncheckedIcon="checkbox-blank-outline" + checkedColor="black" + > + ); + }, + )} diff --git a/src/utils/FilterContext.tsx b/src/utils/FilterContext.tsx new file mode 100644 index 00000000..a85b6e34 --- /dev/null +++ b/src/utils/FilterContext.tsx @@ -0,0 +1,159 @@ +import React, { + createContext, + useContext, + useEffect, + useMemo, + useReducer, +} from 'react'; +import supabase from './supabase'; + +type FilterAction = + | { type: 'SET_TAGS'; tags: TagFilter[] } + | { type: 'TOGGLE_FILTER'; id: number } + | { type: 'CLEAR_ALL'; category: string } + | { type: 'TOGGLE_MAIN_GENRE'; mainGenreId: number }; + +export type FilterDispatch = React.Dispatch; + +export type TagFilter = { + id: number; + name: string; + category: string; + active: boolean; + parent: number | null; +}; + +export interface FilterState { + filters: Map; + isLoading: boolean; + dispatch: FilterDispatch; +} + +const FilterContext = createContext({} as FilterState); + +export const useFilterReducer = () => + useReducer( + (prevState: FilterState, action: FilterAction) => { + switch (action.type) { + case 'SET_TAGS': + const filterMap = new Map(); + action.tags.map(tag => filterMap.set(tag.id, tag)); + + return { + ...prevState, + filters: filterMap, + isLoading: false, + }; + case 'TOGGLE_FILTER': + const desiredTag: TagFilter = + prevState.filters.get(action.id) ?? ({} as TagFilter); + + prevState.filters.set(action.id, { + ...desiredTag, + active: !desiredTag?.active, + }); + return { + ...prevState, + filters: prevState.filters, + }; + case 'CLEAR_ALL': + const clearedFilters = Array.from( + prevState.filters, + ([id, filter]): [number, TagFilter] => + filter.category == action.category + ? [id, { ...filter, active: false }] + : [id, filter], + ); + + return { + ...prevState, + filters: new Map(clearedFilters), + }; + case 'TOGGLE_MAIN_GENRE': + const parentGenre = prevState.filters.get(action.mainGenreId); + const newActiveState = !parentGenre?.active; + + const updatedFilters = Array.from( + prevState.filters, + ([id, filter]): [number, TagFilter] => + filter.parent == parentGenre?.id || id == action.mainGenreId + ? [id, { ...filter, active: newActiveState }] + : [id, filter], + ); + + return { + ...prevState, + filters: new Map(updatedFilters), + }; + default: + return prevState; + } + }, + { + filters: new Map(), + isLoading: true, + dispatch: () => null, + }, + ); + +export function useFilter() { + const value = useContext(FilterContext); + if (process.env.NODE_ENV !== 'production') { + if (!value) { + throw new Error( + 'useFilter must be wrapped in a ', + ); + } + } + + return value; +} + +export function FilterContextProvider({ + children, +}: { + children: React.ReactNode; +}) { + const [filterState, dispatch] = useFilterReducer(); + + const initTagsFromSupabase = async () => { + const { data } = await supabase.from('tags').select(`*`); + + return data?.map(entry => { + const { category, id, name, parent_id } = entry; + console.log(entry); + + return { + id, + name, + category, + parent: parent_id, + active: false, + } as TagFilter; + }); + }; + + useEffect(() => { + initTagsFromSupabase().then(tags => + dispatch({ type: 'SET_TAGS', tags: tags ?? [] }), + ); + }, []); + + useEffect(() => { + console.log(filterState); + }, [filterState]); + + const filterContextValue = useMemo( + () => ({ + ...filterState, + dispatch, + }), + [filterState], + ); + + return ( + + {children} + + ); +} From 74e33263e7a365cbb8401c8737fa779731d2e47e Mon Sep 17 00:00:00 2001 From: Aditya Pawar Date: Sat, 2 Dec 2023 14:10:48 -0800 Subject: [PATCH 2/9] With list --- src/components/FilterModal/FilterModal.tsx | 61 ++++++--------------- src/utils/FilterContext.tsx | 64 +++++++--------------- 2 files changed, 38 insertions(+), 87 deletions(-) diff --git a/src/components/FilterModal/FilterModal.tsx b/src/components/FilterModal/FilterModal.tsx index 72ba1d8c..8917491a 100644 --- a/src/components/FilterModal/FilterModal.tsx +++ b/src/components/FilterModal/FilterModal.tsx @@ -1,4 +1,5 @@ import { BottomSheet, CheckBox } from '@rneui/themed'; +import { useState } from 'react'; import { View, Text, ScrollView, Pressable } from 'react-native'; import { SafeAreaProvider } from 'react-native-safe-area-context'; @@ -6,7 +7,6 @@ import 'react-native-gesture-handler'; import styles from './styles'; import Icon from '../../../assets/icons'; import { TagFilter, useFilter } from '../../utils/FilterContext'; -import { useEffect, useRef } from 'react'; type FilterModalProps = { isVisible: boolean; @@ -14,33 +14,8 @@ type FilterModalProps = { title: string; }; -type ParentTagFilter = { children: TagFilter[] } & TagFilter; - function FilterModal({ isVisible, setIsVisible, title }: FilterModalProps) { const { dispatch, filters } = useFilter(); - const nestedFilters = useRef(new Map()); - - useEffect(() => { - nestFilters(); - // console.log(nestedFilters.current); - }, [filters]); - - const nestFilters = () => { - Array.from(filters) - .filter(([_, { parent }]) => parent === null) - .map(([id, parent]) => - nestedFilters.current.set(id, { - ...parent, - children: [], - } as ParentTagFilter), - ); - - Array.from(filters).map(([_, filter]) => { - if (filter.parent) { - nestedFilters.current.get(filter.parent)?.children.push(filter); - } - }); - }; return ( @@ -65,24 +40,22 @@ function FilterModal({ isVisible, setIsVisible, title }: FilterModalProps) { bounces={false} style={styles.scrollView} > - {Array.from(nestedFilters.current).map( - ([id, parentFilter]: [number, ParentTagFilter]) => { - return ( - - dispatch({ type: 'TOGGLE_FILTER', id: parentFilter.id }) - } - iconType="material-community" - checkedIcon="checkbox-marked" - uncheckedIcon="checkbox-blank-outline" - checkedColor="black" - > - ); - }, - )} + {filters.map(filter => { + return ( + + dispatch({ type: 'TOGGLE_FILTER', name: filter.name }) + } + iconType="material-community" + checkedIcon="checkbox-marked" + uncheckedIcon="checkbox-blank-outline" + checkedColor="black" + /> + ); + })} diff --git a/src/utils/FilterContext.tsx b/src/utils/FilterContext.tsx index a85b6e34..2cfe9f2c 100644 --- a/src/utils/FilterContext.tsx +++ b/src/utils/FilterContext.tsx @@ -9,9 +9,9 @@ import supabase from './supabase'; type FilterAction = | { type: 'SET_TAGS'; tags: TagFilter[] } - | { type: 'TOGGLE_FILTER'; id: number } + | { type: 'TOGGLE_FILTER'; name: string } | { type: 'CLEAR_ALL'; category: string } - | { type: 'TOGGLE_MAIN_GENRE'; mainGenreId: number }; + | { type: 'TOGGLE_MAIN_GENRE'; mainGenre: string }; export type FilterDispatch = React.Dispatch; @@ -24,7 +24,7 @@ export type TagFilter = { }; export interface FilterState { - filters: Map; + filters: TagFilter[]; isLoading: boolean; dispatch: FilterDispatch; } @@ -36,61 +36,47 @@ export const useFilterReducer = () => (prevState: FilterState, action: FilterAction) => { switch (action.type) { case 'SET_TAGS': - const filterMap = new Map(); - action.tags.map(tag => filterMap.set(tag.id, tag)); - return { ...prevState, - filters: filterMap, + filters: action.tags, isLoading: false, }; case 'TOGGLE_FILTER': - const desiredTag: TagFilter = - prevState.filters.get(action.id) ?? ({} as TagFilter); - - prevState.filters.set(action.id, { - ...desiredTag, - active: !desiredTag?.active, - }); return { ...prevState, - filters: prevState.filters, + filters: prevState.filters.map(tag => + tag.name == action.name ? { ...tag, active: !tag.active } : tag, + ), }; case 'CLEAR_ALL': - const clearedFilters = Array.from( - prevState.filters, - ([id, filter]): [number, TagFilter] => - filter.category == action.category - ? [id, { ...filter, active: false }] - : [id, filter], - ); - return { ...prevState, - filters: new Map(clearedFilters), + filters: prevState.filters.map(tag => + tag.category == action.category ? { ...tag, active: false } : tag, + ), }; case 'TOGGLE_MAIN_GENRE': - const parentGenre = prevState.filters.get(action.mainGenreId); + const parentGenre = prevState.filters.find( + ({ name }) => name === action.mainGenre, + ); const newActiveState = !parentGenre?.active; - const updatedFilters = Array.from( - prevState.filters, - ([id, filter]): [number, TagFilter] => - filter.parent == parentGenre?.id || id == action.mainGenreId - ? [id, { ...filter, active: newActiveState }] - : [id, filter], + const updatedFilters = prevState.filters.map(tag => + tag.parent == parentGenre?.id || tag.name == action.mainGenre + ? { ...tag, active: newActiveState } + : tag, ); return { ...prevState, - filters: new Map(updatedFilters), + filters: updatedFilters, }; default: return prevState; } }, { - filters: new Map(), + filters: [], isLoading: true, dispatch: () => null, }, @@ -116,13 +102,11 @@ export function FilterContextProvider({ }) { const [filterState, dispatch] = useFilterReducer(); - const initTagsFromSupabase = async () => { + const getTags = async () => { const { data } = await supabase.from('tags').select(`*`); return data?.map(entry => { const { category, id, name, parent_id } = entry; - console.log(entry); - return { id, name, @@ -134,15 +118,9 @@ export function FilterContextProvider({ }; useEffect(() => { - initTagsFromSupabase().then(tags => - dispatch({ type: 'SET_TAGS', tags: tags ?? [] }), - ); + getTags().then(tags => dispatch({ type: 'SET_TAGS', tags: tags ?? [] })); }, []); - useEffect(() => { - console.log(filterState); - }, [filterState]); - const filterContextValue = useMemo( () => ({ ...filterState, From 6a58965160f9df247b7d797f23d684647e6db2b7 Mon Sep 17 00:00:00 2001 From: Aditya Pawar Date: Sat, 2 Dec 2023 14:57:10 -0800 Subject: [PATCH 3/9] Slow af but working --- src/components/FilterModal/FilterModal.tsx | 79 ++++++++++++++++++---- src/utils/FilterContext.tsx | 10 ++- 2 files changed, 74 insertions(+), 15 deletions(-) diff --git a/src/components/FilterModal/FilterModal.tsx b/src/components/FilterModal/FilterModal.tsx index 8917491a..c795d30c 100644 --- a/src/components/FilterModal/FilterModal.tsx +++ b/src/components/FilterModal/FilterModal.tsx @@ -14,9 +14,31 @@ type FilterModalProps = { title: string; }; +type ParentFilter = { children: TagFilter[] } & TagFilter; + function FilterModal({ isVisible, setIsVisible, title }: FilterModalProps) { const { dispatch, filters } = useFilter(); + const nestFilters = (filters: TagFilter[]) => { + const parents = new Map(); + filters + .filter(filter => filter.parent === null) + .map(parentFilter => { + parents.set(parentFilter.id, { + ...parentFilter, + children: [], + } as ParentFilter); + }); + + filters.map(childFilter => { + if (childFilter.parent) { + parents.get(childFilter.parent)?.children.push(childFilter); + } + }); + + return parents; + }; + return ( {title} - {filters.map(filter => { + {Array.from(nestFilters(filters)).map(([id, parentFilter]) => { + console.log('rerendering ' + parentFilter.name); return ( - - dispatch({ type: 'TOGGLE_FILTER', name: filter.name }) - } - iconType="material-community" - checkedIcon="checkbox-marked" - uncheckedIcon="checkbox-blank-outline" - checkedColor="black" - /> + <> + + dispatch({ + type: 'TOGGLE_FILTER', + name: parentFilter.name, + }) + } + iconType="material-community" + checkedIcon="checkbox-marked" + uncheckedIcon="checkbox-blank-outline" + checkedColor="black" + /> + + {parentFilter.children.map(filter => { + console.log('rerendering ' + filter.name); + + return ( + + dispatch({ + type: 'TOGGLE_FILTER', + name: filter.name, + }) + } + iconType="material-community" + checkedIcon="checkbox-marked" + uncheckedIcon="checkbox-blank-outline" + checkedColor="black" + /> + ); + })} + ); })} diff --git a/src/utils/FilterContext.tsx b/src/utils/FilterContext.tsx index 2cfe9f2c..e586d470 100644 --- a/src/utils/FilterContext.tsx +++ b/src/utils/FilterContext.tsx @@ -42,12 +42,16 @@ export const useFilterReducer = () => isLoading: false, }; case 'TOGGLE_FILTER': - return { + const start = +Date.now(); + const nextState = { ...prevState, filters: prevState.filters.map(tag => tag.name == action.name ? { ...tag, active: !tag.active } : tag, ), }; + console.log('Toggle time' + (+Date.now() - start)); + + return nextState; case 'CLEAR_ALL': return { ...prevState, @@ -121,6 +125,10 @@ export function FilterContextProvider({ getTags().then(tags => dispatch({ type: 'SET_TAGS', tags: tags ?? [] })); }, []); + useEffect(() => { + console.log('shit'); + }, [filterState.filters[0]]); + const filterContextValue = useMemo( () => ({ ...filterState, From 37c121b553e428bf34c838f4ac6bc544273e3fad Mon Sep 17 00:00:00 2001 From: Aditya Pawar Date: Sat, 2 Dec 2023 15:15:33 -0800 Subject: [PATCH 4/9] Fast af and working --- src/components/FilterModal/ChildFilter.tsx | 27 ++++++++++++ src/components/FilterModal/FilterModal.tsx | 48 ++++++++------------- src/components/FilterModal/ParentFilter.tsx | 26 +++++++++++ src/utils/FilterContext.tsx | 4 +- 4 files changed, 74 insertions(+), 31 deletions(-) create mode 100644 src/components/FilterModal/ChildFilter.tsx create mode 100644 src/components/FilterModal/ParentFilter.tsx diff --git a/src/components/FilterModal/ChildFilter.tsx b/src/components/FilterModal/ChildFilter.tsx new file mode 100644 index 00000000..3b93abd1 --- /dev/null +++ b/src/components/FilterModal/ChildFilter.tsx @@ -0,0 +1,27 @@ +import { CheckBox } from '@rneui/themed'; +import { memo } from 'react'; + +type ChildFilterProps = { + id: number; + name: string; + checked: boolean; + onPress: (id: number) => void; +}; + +function ChildFilter({ id, name, checked, onPress }: ChildFilterProps) { + return ( + onPress(id)} + iconType="material-community" + checkedIcon="checkbox-marked" + uncheckedIcon="checkbox-blank-outline" + checkedColor="black" + /> + ); +} + +export default memo(ChildFilter); diff --git a/src/components/FilterModal/FilterModal.tsx b/src/components/FilterModal/FilterModal.tsx index c795d30c..a86664dd 100644 --- a/src/components/FilterModal/FilterModal.tsx +++ b/src/components/FilterModal/FilterModal.tsx @@ -1,5 +1,5 @@ import { BottomSheet, CheckBox } from '@rneui/themed'; -import { useState } from 'react'; +import { useCallback, useState } from 'react'; import { View, Text, ScrollView, Pressable } from 'react-native'; import { SafeAreaProvider } from 'react-native-safe-area-context'; @@ -7,6 +7,8 @@ import 'react-native-gesture-handler'; import styles from './styles'; import Icon from '../../../assets/icons'; import { TagFilter, useFilter } from '../../utils/FilterContext'; +import ChildFilter from './ChildFilter'; +import ParentFilter from './ParentFilter'; type FilterModalProps = { isVisible: boolean; @@ -19,6 +21,13 @@ type ParentFilter = { children: TagFilter[] } & TagFilter; function FilterModal({ isVisible, setIsVisible, title }: FilterModalProps) { const { dispatch, filters } = useFilter(); + const toggleFilter = useCallback( + (id: number) => { + dispatch({ type: 'TOGGLE_FILTER', id }); + }, + [dispatch], + ); + const nestFilters = (filters: TagFilter[]) => { const parents = new Map(); filters @@ -62,45 +71,26 @@ function FilterModal({ isVisible, setIsVisible, title }: FilterModalProps) { bounces={true} style={styles.scrollView} > - {Array.from(nestFilters(filters)).map(([id, parentFilter]) => { + {Array.from(nestFilters(filters)).map(([_, parentFilter]) => { console.log('rerendering ' + parentFilter.name); return ( <> - - dispatch({ - type: 'TOGGLE_FILTER', - name: parentFilter.name, - }) - } - iconType="material-community" - checkedIcon="checkbox-marked" - uncheckedIcon="checkbox-blank-outline" - checkedColor="black" + onPress={toggleFilter} /> {parentFilter.children.map(filter => { console.log('rerendering ' + filter.name); return ( - - dispatch({ - type: 'TOGGLE_FILTER', - name: filter.name, - }) - } - iconType="material-community" - checkedIcon="checkbox-marked" - uncheckedIcon="checkbox-blank-outline" - checkedColor="black" + onPress={toggleFilter} /> ); })} diff --git a/src/components/FilterModal/ParentFilter.tsx b/src/components/FilterModal/ParentFilter.tsx new file mode 100644 index 00000000..d763e0a5 --- /dev/null +++ b/src/components/FilterModal/ParentFilter.tsx @@ -0,0 +1,26 @@ +import { CheckBox } from '@rneui/themed'; +import { memo } from 'react'; + +type ParentFilterProps = { + id: number; + name: string; + checked: boolean; + onPress: (id: number) => void; +}; + +function ParentFilter({ id, name, checked, onPress }: ParentFilterProps) { + return ( + onPress(id)} + iconType="material-community" + checkedIcon="checkbox-marked" + uncheckedIcon="checkbox-blank-outline" + checkedColor="black" + /> + ); +} + +export default memo(ParentFilter); diff --git a/src/utils/FilterContext.tsx b/src/utils/FilterContext.tsx index e586d470..14d590a6 100644 --- a/src/utils/FilterContext.tsx +++ b/src/utils/FilterContext.tsx @@ -9,7 +9,7 @@ import supabase from './supabase'; type FilterAction = | { type: 'SET_TAGS'; tags: TagFilter[] } - | { type: 'TOGGLE_FILTER'; name: string } + | { type: 'TOGGLE_FILTER'; id: number } | { type: 'CLEAR_ALL'; category: string } | { type: 'TOGGLE_MAIN_GENRE'; mainGenre: string }; @@ -46,7 +46,7 @@ export const useFilterReducer = () => const nextState = { ...prevState, filters: prevState.filters.map(tag => - tag.name == action.name ? { ...tag, active: !tag.active } : tag, + tag.id == action.id ? { ...tag, active: !tag.active } : tag, ), }; console.log('Toggle time' + (+Date.now() - start)); From 5459e842b03ceb9268ca7c748bf8380cee78a2ae Mon Sep 17 00:00:00 2001 From: Aditya Pawar Date: Sat, 2 Dec 2023 15:34:09 -0800 Subject: [PATCH 5/9] Parent selection working --- src/components/FilterModal/FilterModal.tsx | 40 +++-------- src/utils/FilterContext.tsx | 79 +++++++++++++++------- 2 files changed, 66 insertions(+), 53 deletions(-) diff --git a/src/components/FilterModal/FilterModal.tsx b/src/components/FilterModal/FilterModal.tsx index a86664dd..07f1b005 100644 --- a/src/components/FilterModal/FilterModal.tsx +++ b/src/components/FilterModal/FilterModal.tsx @@ -16,37 +16,22 @@ type FilterModalProps = { title: string; }; -type ParentFilter = { children: TagFilter[] } & TagFilter; - function FilterModal({ isVisible, setIsVisible, title }: FilterModalProps) { const { dispatch, filters } = useFilter(); - const toggleFilter = useCallback( + const toggleParentFilter = useCallback( (id: number) => { - dispatch({ type: 'TOGGLE_FILTER', id }); + dispatch({ type: 'TOGGLE_MAIN_GENRE', mainGenreId: id }); }, [dispatch], ); - const nestFilters = (filters: TagFilter[]) => { - const parents = new Map(); - filters - .filter(filter => filter.parent === null) - .map(parentFilter => { - parents.set(parentFilter.id, { - ...parentFilter, - children: [], - } as ParentFilter); - }); - - filters.map(childFilter => { - if (childFilter.parent) { - parents.get(childFilter.parent)?.children.push(childFilter); - } - }); - - return parents; - }; + const toggleChildFilter = useCallback( + (id: number) => { + dispatch({ type: 'TOGGLE_FILTER', id }); + }, + [dispatch], + ); return ( @@ -71,26 +56,23 @@ function FilterModal({ isVisible, setIsVisible, title }: FilterModalProps) { bounces={true} style={styles.scrollView} > - {Array.from(nestFilters(filters)).map(([_, parentFilter]) => { - console.log('rerendering ' + parentFilter.name); + {Array.from(filters).map(([_, parentFilter]) => { return ( <> {parentFilter.children.map(filter => { - console.log('rerendering ' + filter.name); - return ( ); })} diff --git a/src/utils/FilterContext.tsx b/src/utils/FilterContext.tsx index 14d590a6..e98c7771 100644 --- a/src/utils/FilterContext.tsx +++ b/src/utils/FilterContext.tsx @@ -11,7 +11,7 @@ type FilterAction = | { type: 'SET_TAGS'; tags: TagFilter[] } | { type: 'TOGGLE_FILTER'; id: number } | { type: 'CLEAR_ALL'; category: string } - | { type: 'TOGGLE_MAIN_GENRE'; mainGenre: string }; + | { type: 'TOGGLE_MAIN_GENRE'; mainGenreId: number }; export type FilterDispatch = React.Dispatch; @@ -23,52 +23,87 @@ export type TagFilter = { parent: number | null; }; +type ParentFilter = { children: TagFilter[] } & TagFilter; + export interface FilterState { - filters: TagFilter[]; + filters: Map; isLoading: boolean; dispatch: FilterDispatch; } const FilterContext = createContext({} as FilterState); +const mapParentsAndChildren = ( + filters: Map, + func: (filter: TagFilter) => TagFilter, +) => { + return new Map( + Array.from(filters).map(([id, parent]) => { + return [ + id, + { + ...func(parent), + children: parent.children.map(func), + } as ParentFilter, + ]; + }), + ); +}; + export const useFilterReducer = () => useReducer( (prevState: FilterState, action: FilterAction) => { switch (action.type) { case 'SET_TAGS': + const nestedFilters = new Map(); + action.tags + .filter(filter => filter.parent === null) + .map(parentFilter => { + nestedFilters.set(parentFilter.id, { + ...parentFilter, + children: [], + } as ParentFilter); + }); + + action.tags.map(childFilter => { + if (childFilter.parent) { + nestedFilters.get(childFilter.parent)?.children.push(childFilter); + } + }); + return { ...prevState, - filters: action.tags, + filters: nestedFilters, isLoading: false, }; case 'TOGGLE_FILTER': - const start = +Date.now(); - const nextState = { + return { ...prevState, - filters: prevState.filters.map(tag => - tag.id == action.id ? { ...tag, active: !tag.active } : tag, + filters: mapParentsAndChildren(prevState.filters, fitler => + fitler.id == action.id + ? { ...fitler, active: !fitler.active } + : fitler, ), }; - console.log('Toggle time' + (+Date.now() - start)); - - return nextState; case 'CLEAR_ALL': return { ...prevState, - filters: prevState.filters.map(tag => - tag.category == action.category ? { ...tag, active: false } : tag, + filters: mapParentsAndChildren(prevState.filters, filter => + filter.category == action.category + ? { ...filter, active: false } + : filter, ), }; case 'TOGGLE_MAIN_GENRE': - const parentGenre = prevState.filters.find( - ({ name }) => name === action.mainGenre, - ); + const parentGenre = prevState.filters.get(action.mainGenreId); const newActiveState = !parentGenre?.active; - const updatedFilters = prevState.filters.map(tag => - tag.parent == parentGenre?.id || tag.name == action.mainGenre - ? { ...tag, active: newActiveState } - : tag, + const updatedFilters = mapParentsAndChildren( + prevState.filters, + tag => + tag.parent == action.mainGenreId || tag.id == action.mainGenreId + ? { ...tag, active: newActiveState } + : tag, ); return { @@ -80,7 +115,7 @@ export const useFilterReducer = () => } }, { - filters: [], + filters: new Map(), isLoading: true, dispatch: () => null, }, @@ -125,10 +160,6 @@ export function FilterContextProvider({ getTags().then(tags => dispatch({ type: 'SET_TAGS', tags: tags ?? [] })); }, []); - useEffect(() => { - console.log('shit'); - }, [filterState.filters[0]]); - const filterContextValue = useMemo( () => ({ ...filterState, From 68843ef04f6e7d438f3cb2e2fc2e5273e8471888 Mon Sep 17 00:00:00 2001 From: Aditya Pawar Date: Sat, 2 Dec 2023 16:32:48 -0800 Subject: [PATCH 6/9] Filtering works but is slow af --- src/app/(tabs)/search/index.tsx | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/app/(tabs)/search/index.tsx b/src/app/(tabs)/search/index.tsx index f872ce24..cb2a9fde 100644 --- a/src/app/(tabs)/search/index.tsx +++ b/src/app/(tabs)/search/index.tsx @@ -22,7 +22,8 @@ import { fetchAllStoryPreviews } from '../../../queries/stories'; import { StoryPreview, RecentSearch, Genre } from '../../../queries/types'; import colors from '../../../styles/colors'; import globalStyles from '../../../styles/globalStyles'; -import { useFilter } from '../../../utils/FilterContext'; +import { TagFilter, useFilter } from '../../../utils/FilterContext'; +import { ActivityIndicator } from 'react-native-paper'; const getRecentSearch = async () => { try { @@ -61,6 +62,7 @@ const setRecentStory = async (recentStories: StoryPreview[]) => { }; function SearchScreen() { + const { filters } = useFilter(); const [allStories, setAllStories] = useState([]); const [allGenres, setAllGenres] = useState([]); const [searchResults, setSearchResults] = useState([]); @@ -82,6 +84,10 @@ function SearchScreen() { })(); }, []); + useEffect(() => { + if (!filterVisible) searchFunction(search); + }, [filterVisible]); + const getColor = (index: number) => { const genreColors = [colors.citrus, colors.lime, colors.lilac]; return genreColors[index % genreColors.length]; @@ -93,11 +99,26 @@ function SearchScreen() { setSearchResults([]); return; } + const flattenenedFilters = Array.from(filters) + .map(([id, parent]) => [...parent.children, parent as TagFilter]) + .flat(); + const activeFilterNames = flattenenedFilters + .filter(({ active }) => active) + .map(({ name }) => name); + const updatedData = allStories.filter((item: StoryPreview) => { const title = `${item.title.toUpperCase()})`; const author = `${item.author_name.toUpperCase()})`; const text_data = text.toUpperCase(); - return title.indexOf(text_data) > -1 || author.indexOf(text_data) > -1; + + const matchesFilter = + activeFilterNames.length == 0 || + item.genre_medium.some(genre => activeFilterNames.includes(genre)); + + return ( + (title.indexOf(text_data) > -1 || author.indexOf(text_data) > -1) && + matchesFilter + ); }); setSearch(text); setSearchResults(updatedData); From 96f9a53b03a37435dda52fcb4584c2db562c690b Mon Sep 17 00:00:00 2001 From: Aditya Pawar Date: Sat, 2 Dec 2023 16:46:48 -0800 Subject: [PATCH 7/9] Finish filtering functionality --- src/app/(tabs)/search/index.tsx | 23 ++++++++++++++++++---- src/components/FilterModal/FilterModal.tsx | 6 ++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/app/(tabs)/search/index.tsx b/src/app/(tabs)/search/index.tsx index cb2a9fde..02ce284b 100644 --- a/src/app/(tabs)/search/index.tsx +++ b/src/app/(tabs)/search/index.tsx @@ -13,7 +13,9 @@ import { import { SafeAreaView } from 'react-native-safe-area-context'; import styles from './styles'; -import FilterModal from '../../../components/FilterModal/FilterModal'; +import FilterModal, { + CATEGORIES as FilterCategories, +} from '../../../components/FilterModal/FilterModal'; import GenreCard from '../../../components/GenreCard/GenreCard'; import PreviewCard from '../../../components/PreviewCard/PreviewCard'; import RecentSearchCard from '../../../components/RecentSearchCard/RecentSearchCard'; @@ -102,8 +104,16 @@ function SearchScreen() { const flattenenedFilters = Array.from(filters) .map(([id, parent]) => [...parent.children, parent as TagFilter]) .flat(); - const activeFilterNames = flattenenedFilters - .filter(({ active }) => active) + const activeFilterNames = flattenenedFilters.filter(({ active }) => active); + + const activeGenreNames = activeFilterNames + .filter(({ category }) => category == FilterCategories.GENRE) + .map(({ name }) => name); + const activeToneNames = activeFilterNames + .filter(({ category }) => category == FilterCategories.TONE) + .map(({ name }) => name); + const activeTopicNames = activeFilterNames + .filter(({ category }) => category == FilterCategories.TOPIC) .map(({ name }) => name); const updatedData = allStories.filter((item: StoryPreview) => { @@ -113,7 +123,12 @@ function SearchScreen() { const matchesFilter = activeFilterNames.length == 0 || - item.genre_medium.some(genre => activeFilterNames.includes(genre)); + ((activeGenreNames.length == 0 || + item.genre_medium.some(genre => activeGenreNames.includes(genre))) && + (activeToneNames.length == 0 || + item.tone.some(tone => activeToneNames.includes(tone))) && + (activeTopicNames.length == 0 || + item.topic.some(topic => activeTopicNames.includes(topic)))); return ( (title.indexOf(text_data) > -1 || author.indexOf(text_data) > -1) && diff --git a/src/components/FilterModal/FilterModal.tsx b/src/components/FilterModal/FilterModal.tsx index 07f1b005..7d8b647c 100644 --- a/src/components/FilterModal/FilterModal.tsx +++ b/src/components/FilterModal/FilterModal.tsx @@ -16,6 +16,12 @@ type FilterModalProps = { title: string; }; +export enum CATEGORIES { + GENRE = 'genre-medium', + TOPIC = 'topic', + TONE = 'tone', +} + function FilterModal({ isVisible, setIsVisible, title }: FilterModalProps) { const { dispatch, filters } = useFilter(); From 1cf5bdd0a08b63beb92ecd14a3dad17cc116ffab Mon Sep 17 00:00:00 2001 From: Aditya Pawar Date: Sat, 2 Dec 2023 16:55:46 -0800 Subject: [PATCH 8/9] Implement flat list but still slow af --- src/components/FilterModal/FilterModal.tsx | 39 +++++++++++----------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/components/FilterModal/FilterModal.tsx b/src/components/FilterModal/FilterModal.tsx index 7d8b647c..9994dff2 100644 --- a/src/components/FilterModal/FilterModal.tsx +++ b/src/components/FilterModal/FilterModal.tsx @@ -1,6 +1,6 @@ import { BottomSheet, CheckBox } from '@rneui/themed'; import { useCallback, useState } from 'react'; -import { View, Text, ScrollView, Pressable } from 'react-native'; +import { View, Text, ScrollView, Pressable, FlatList } from 'react-native'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import 'react-native-gesture-handler'; @@ -57,12 +57,10 @@ function FilterModal({ isVisible, setIsVisible, title }: FilterModalProps) { {title} - - {Array.from(filters).map(([_, parentFilter]) => { + { + const [_, parentFilter] = item; return ( <> - {parentFilter.children.map(filter => { - return ( - - ); - })} + { + return ( + + ); + }} + /> ); - })} - + }} + /> From bf176f94ee60d45ca2deebaa4d5907391be18deb Mon Sep 17 00:00:00 2001 From: Aditya Pawar Date: Sat, 2 Dec 2023 17:13:36 -0800 Subject: [PATCH 9/9] Final changes --- src/components/FilterModal/FilterModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/FilterModal/FilterModal.tsx b/src/components/FilterModal/FilterModal.tsx index 9994dff2..0f9ca16a 100644 --- a/src/components/FilterModal/FilterModal.tsx +++ b/src/components/FilterModal/FilterModal.tsx @@ -1,6 +1,7 @@ import { BottomSheet, CheckBox } from '@rneui/themed'; import { useCallback, useState } from 'react'; -import { View, Text, ScrollView, Pressable, FlatList } from 'react-native'; +import { View, Text, ScrollView, Pressable } from 'react-native'; +import { FlatList } from 'react-native-gesture-handler'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import 'react-native-gesture-handler'; @@ -42,7 +43,6 @@ function FilterModal({ isVisible, setIsVisible, title }: FilterModalProps) { return (