diff --git a/package-lock.json b/package-lock.json
index aac3b82a..ccd4c392 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,6 +12,9 @@
"@emotion/styled": "^11.11.0",
"@expo-google-fonts/manrope": "^0.2.3",
"@expo/vector-icons": "^13.0.0",
+ "@fortawesome/fontawesome-svg-core": "^6.5.2",
+ "@fortawesome/free-solid-svg-icons": "^6.5.2",
+ "@fortawesome/react-native-fontawesome": "^0.3.0",
"@mui/icons-material": "^5.14.13",
"@mui/material": "^5.14.13",
"@mui/styled-engine-sc": "^6.0.0-alpha.1",
@@ -3641,6 +3644,53 @@
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz",
"integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A=="
},
+ "node_modules/@fortawesome/fontawesome-common-types": {
+ "version": "6.5.2",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.2.tgz",
+ "integrity": "sha512-gBxPg3aVO6J0kpfHNILc+NMhXnqHumFxOmjYCFfOiLZfwhnnfhtsdA2hfJlDnj+8PjAs6kKQPenOTKj3Rf7zHw==",
+ "hasInstallScript": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@fortawesome/fontawesome-svg-core": {
+ "version": "6.5.2",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.2.tgz",
+ "integrity": "sha512-5CdaCBGl8Rh9ohNdxeeTMxIj8oc3KNBgIeLMvJosBMdslK/UnEB8rzyDRrbKdL1kDweqBPo4GT9wvnakHWucZw==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@fortawesome/fontawesome-common-types": "6.5.2"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@fortawesome/free-solid-svg-icons": {
+ "version": "6.5.2",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.2.tgz",
+ "integrity": "sha512-QWFZYXFE7O1Gr1dTIp+D6UcFUF0qElOnZptpi7PBUMylJh+vFmIedVe1Ir6RM1t2tEQLLSV1k7bR4o92M+uqlw==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@fortawesome/fontawesome-common-types": "6.5.2"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@fortawesome/react-native-fontawesome": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/react-native-fontawesome/-/react-native-fontawesome-0.3.0.tgz",
+ "integrity": "sha512-wSfetdK4+b/pvPbM2v+bZ5hfNlwtk9l3QuJo59sbMrxJalfX7BuF2WsSIWMSxfWwSsbOtY4+TUs6uw/rE59NJA==",
+ "dependencies": {
+ "humps": "^2.0.1",
+ "prop-types": "^15.7.2"
+ },
+ "peerDependencies": {
+ "@fortawesome/fontawesome-svg-core": "~1 || ~6",
+ "react-native": ">= 0.67",
+ "react-native-svg": ">= 11.x"
+ }
+ },
"node_modules/@gar/promisify": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
@@ -12127,6 +12177,11 @@
"node": ">=10.17.0"
}
},
+ "node_modules/humps": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/humps/-/humps-2.0.1.tgz",
+ "integrity": "sha512-E0eIbrFWUhwfXJmsbdjRQFQPrl5pTEoKlz163j1mTqqUnU9PgR4AgB8AIITzuB3vLBdxZXyZ9TDIrwB2OASz4g=="
+ },
"node_modules/husky": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz",
diff --git a/package.json b/package.json
index 3e8c0f7b..ca31a24f 100644
--- a/package.json
+++ b/package.json
@@ -16,6 +16,9 @@
"@emotion/styled": "^11.11.0",
"@expo-google-fonts/manrope": "^0.2.3",
"@expo/vector-icons": "^13.0.0",
+ "@fortawesome/fontawesome-svg-core": "^6.5.2",
+ "@fortawesome/free-solid-svg-icons": "^6.5.2",
+ "@fortawesome/react-native-fontawesome": "^0.3.0",
"@mui/icons-material": "^5.14.13",
"@mui/material": "^5.14.13",
"@mui/styled-engine-sc": "^6.0.0-alpha.1",
diff --git a/src/app/(tabs)/genre/_layout.tsx b/src/app/(tabs)/genre/_layout.tsx
index d021a461..d309c307 100644
--- a/src/app/(tabs)/genre/_layout.tsx
+++ b/src/app/(tabs)/genre/_layout.tsx
@@ -1,10 +1,14 @@
import { Stack } from 'expo-router';
+import { FilterContextProvider } from '../../../utils/FilterContext';
+
function StackLayout() {
return (
-
-
-
+
+
+
+
+
);
}
diff --git a/src/app/(tabs)/genre/index.tsx b/src/app/(tabs)/genre/index.tsx
index 92edca0a..a5b82774 100644
--- a/src/app/(tabs)/genre/index.tsx
+++ b/src/app/(tabs)/genre/index.tsx
@@ -14,15 +14,18 @@ import { SafeAreaView } from 'react-native-safe-area-context';
import styles from './styles';
import BackButton from '../../../components/BackButton/BackButton';
+import FilterModal, {
+ CATEGORIES as FilterCategories,
+} from '../../../components/FilterModal/FilterModal';
import GenreStoryPreviewCard from '../../../components/GenreStoryPreviewCard/GenreStoryPreviewCard';
import { fetchGenreStoryPreviews, fetchGenres } from '../../../queries/genres';
import { fetchStoryPreviewById } from '../../../queries/stories';
import { StoryPreview, GenreStories, Genre } from '../../../queries/types';
import globalStyles from '../../../styles/globalStyles';
-
-//TODO figure out the logic for the tone and topic dropdowns, especially when we're dealing with multiselect on both parts
+import { TagFilter, useFilter } from '../../../utils/FilterContext';
function GenreScreen() {
+ const { filters } = useFilter();
const [genreStoryInfo, setGenreStoryInfo] = useState();
const [genreStoryIds, setGenreStoryIds] = useState([]);
const [subgenres, setSubgenres] = useState([]);
@@ -148,6 +151,21 @@ function GenreScreen() {
getGenre();
}, [genreName]);
+ const flattenenedFilters = Array.from(filters)
+ .map(([id, parent]) => [...parent.children, parent as TagFilter])
+ .flat();
+ 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);
+
useEffect(() => {
const showAllStoryPreviews = async () => {
if (genreStoryIds.length > 0) {
diff --git a/src/app/(tabs)/home/index.tsx b/src/app/(tabs)/home/index.tsx
index f9067788..7be7bd9f 100644
--- a/src/app/(tabs)/home/index.tsx
+++ b/src/app/(tabs)/home/index.tsx
@@ -7,6 +7,7 @@ import styles from './styles';
import Icon from '../../../../assets/icons';
import ContentCard from '../../../components/ContentCard/ContentCard';
import PreviewCard from '../../../components/PreviewCard/PreviewCard';
+import ReactionPicker from '../../../components/ReactionPicker/ReactionPicker';
import RecentSearchCard from '../../../components/RecentSearchCard/RecentSearchCard';
import { fetchUsername } from '../../../queries/profiles';
import {
@@ -113,6 +114,7 @@ function HomeScreen() {
Recommended For You
+
Back To Top
+
+
+
)}
diff --git a/src/app/(tabs)/story/styles.ts b/src/app/(tabs)/story/styles.ts
index cbe432a8..67b6ce58 100644
--- a/src/app/(tabs)/story/styles.ts
+++ b/src/app/(tabs)/story/styles.ts
@@ -114,6 +114,10 @@ const styles = StyleSheet.create({
textAlign: 'left',
color: 'black',
},
+ bottomReactionContainer: {
+ flex: 1,
+ justifyContent: 'space-around',
+ },
});
export default styles;
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 c2ded653..16fc7263 100644
--- a/src/components/FilterModal/FilterModal.tsx
+++ b/src/components/FilterModal/FilterModal.tsx
@@ -1,11 +1,14 @@
import { BottomSheet, CheckBox } from '@rneui/themed';
-import { useState } from 'react';
+import { useCallback, useState } from 'react';
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';
+import ChildFilter from './ChildFilter';
+import ParentFilter from './ParentFilter';
import styles from './styles';
import Icon from '../../../assets/icons';
+import { TagFilter, useFilter } from '../../utils/FilterContext';
type FilterModalProps = {
isVisible: boolean;
@@ -13,33 +16,32 @@ type FilterModalProps = {
title: string;
};
+export enum CATEGORIES {
+ GENRE = 'genre-medium',
+ TOPIC = 'topic',
+ TONE = 'tone',
+}
+
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 genres = [
- {
- title: 'Fiction',
- state: checked1,
- setState: toggleChecked1,
+ const toggleParentFilter = useCallback(
+ (id: number) => {
+ dispatch({ type: 'TOGGLE_MAIN_GENRE', mainGenreId: id });
},
- {
- title: 'Erasure & Found Poetry',
- state: checked2,
- setState: toggleChecked2,
- },
- {
- title: 'Non-Fiction',
- state: checked3,
- setState: toggleChecked3,
+ [dispatch],
+ );
+
+ const toggleChildFilter = useCallback(
+ (id: number) => {
+ dispatch({ type: 'TOGGLE_FILTER', id });
},
- ];
+ [dispatch],
+ );
return (
{title}
-
- {genres.map(item => {
+ {
+ const [_, parentFilter] = item;
return (
- item.setState(!item.state)}
- iconType="material-community"
- checkedIcon="checkbox-marked"
- uncheckedIcon="checkbox-blank-outline"
- checkedColor="black"
- />
+ <>
+
+
+ {
+ return (
+
+ );
+ }}
+ />
+ >
);
- })}
-
+ }}
+ />
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/components/ReactionPicker/ReactionPicker.tsx b/src/components/ReactionPicker/ReactionPicker.tsx
new file mode 100644
index 00000000..36d1fa7d
--- /dev/null
+++ b/src/components/ReactionPicker/ReactionPicker.tsx
@@ -0,0 +1,47 @@
+import { faFaceSmile } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome';
+import React, { useState } from 'react';
+import { View, TouchableOpacity, StyleSheet, Text } from 'react-native';
+import { Icon } from 'react-native-elements';
+
+import styles from './styles';
+import globalStyles from '../../styles/globalStyles';
+
+const ReactionPicker = () => {
+ const [showReactions, setShowReactions] = useState(false);
+
+ const toggleReactions = () => setShowReactions(!showReactions);
+
+ // Dummy onPress functions
+ const onSmilePress = () => console.log('Smile pressed');
+ const onHeartPress = () => console.log('Heart pressed');
+ const onThumbsUpPress = () => console.log('Thumbs up pressed');
+ const onLaughPress = () => console.log('Laugh pressed');
+
+ return (
+
+
+
+
+ {showReactions && (
+
+
+ ❤️
+
+
+ 🙂
+
+
+ 👍
+
+
+ 😂
+
+
+ )}
+
+
+ );
+};
+
+export default ReactionPicker;
diff --git a/src/components/ReactionPicker/styles.ts b/src/components/ReactionPicker/styles.ts
new file mode 100644
index 00000000..246806cf
--- /dev/null
+++ b/src/components/ReactionPicker/styles.ts
@@ -0,0 +1,28 @@
+import { StyleSheet } from 'react-native';
+
+import colors from '../../styles/colors';
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ justifyContent: 'flex-end',
+ },
+ reactionView: {
+ backgroundColor: '#D9D9D9',
+ borderRadius: 20,
+ padding: 10,
+ alignSelf: 'center',
+ marginBottom: 10,
+ },
+ reactionsContainer: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ padding: 10,
+ position: 'absolute', // Positioning the container above the toggle button
+ bottom: 50,
+ backgroundColor: '#D9D9D9',
+ borderRadius: 20,
+ },
+});
+
+export default styles;
diff --git a/src/utils/FilterContext.tsx b/src/utils/FilterContext.tsx
index eb14f0ef..6b81de5d 100644
--- a/src/utils/FilterContext.tsx
+++ b/src/utils/FilterContext.tsx
@@ -156,7 +156,6 @@ export function FilterContextProvider({
} as TagFilter;
});
};
-
useEffect(() => {
getTags().then(tags => dispatch({ type: 'SET_TAGS', tags: tags ?? [] }));
}, []);