diff --git a/assets/icons.tsx b/assets/icons.tsx index df93d8c..254e1eb 100644 --- a/assets/icons.tsx +++ b/assets/icons.tsx @@ -4,10 +4,12 @@ import { SvgXml } from 'react-native-svg'; export type IconType = | 'home_outline' + | 'share_outline' | 'document_outline' | 'search_outline' | 'close_modal_button' | 'red_x' + | 'share_outline' | 'green_check' | 'hide_password' | 'grey_dot' @@ -21,6 +23,7 @@ export type IconType = const IconSvgs: Record = { home_outline: , + share_outline: , search_outline: , document_outline: , settings_gear: , diff --git a/src/app/(tabs)/genre/index.tsx b/src/app/(tabs)/genre/index.tsx index 5bf00f5..b5d1df5 100644 --- a/src/app/(tabs)/genre/index.tsx +++ b/src/app/(tabs)/genre/index.tsx @@ -7,6 +7,7 @@ import { Text, FlatList, } from 'react-native'; + import { Dropdown, MultiSelect } from 'react-native-element-dropdown'; import { Icon } from 'react-native-elements'; import { TouchableOpacity } from 'react-native-gesture-handler'; diff --git a/src/app/(tabs)/story/index.tsx b/src/app/(tabs)/story/index.tsx index 281359d..91c8b6d 100644 --- a/src/app/(tabs)/story/index.tsx +++ b/src/app/(tabs)/story/index.tsx @@ -1,12 +1,11 @@ -import { useLocalSearchParams, router } from 'expo-router'; +import { router, useLocalSearchParams } from 'expo-router'; +import * as cheerio from 'cheerio'; import React, { useEffect, useState } from 'react'; import { ActivityIndicator, FlatList, ScrollView, - Share, Text, - TouchableOpacity, View, useWindowDimensions, } from 'react-native'; @@ -16,14 +15,15 @@ import { RenderHTML } from 'react-native-render-html'; import { SafeAreaView } from 'react-native-safe-area-context'; import styles from './styles'; -import FavoriteStoryButton from '../../../components/FavoriteStoryButton/FavoriteStoryButton'; -import SaveStoryButton from '../../../components/SaveStoryButton/SaveStoryButton'; +import Icon from '../../../../assets/icons'; +import AuthorImage from '../../../components/AuthorImage/AuthorImage'; import ReactionPicker from '../../../components/ReactionPicker/ReactionPicker'; -import BackButton from '../../../components/BackButton/BackButton'; import { fetchStory } from '../../../queries/stories'; import { Story } from '../../../queries/types'; -import colors from '../../../styles/colors'; import globalStyles, { fonts } from '../../../styles/globalStyles'; +import BackButton from '../../../components/BackButton/BackButton'; +import { TouchableOpacity } from 'react-native-gesture-handler'; +import OptionBar from '../../../components/OptionBar/OptionBar'; function StoryScreen() { const [isLoading, setLoading] = useState(true); @@ -49,144 +49,98 @@ function StoryScreen() { }); }, [storyId]); - const onShare = async () => { - try { - const result = await Share.share({ - message: `Check out this story from Girls Write Now!!!\n${story.link}/`, - }); - if (result.action === Share.sharedAction) { - if (result.activityType) { - // shared with activity type of result.activityType - } else { - // shared - } - } else if (result.action === Share.dismissedAction) { - // dismissed - } - } catch (error) { - console.log(error); - } - }; - return ( {isLoading ? ( ) : ( - - router.back()} /> - - {story?.title} - - { - router.push({ - pathname: '/author', - params: { author: story.author_id.toString() }, - }); - }} + + - - - - By {story.author_name} - - - - - - ( - - - {item} - - + router.back()} /> + + {story?.featured_media ? ( + + ) : ( + No image available )} + + + {story?.title} + + - - - - + + + index.toString()} // Add a key extractor for performance optimization + renderItem={({ item, index }) => ( + + {item} + + )} + /> - - - - - - - - - - Author's Process - - - - - { - router.push({ - pathname: '/author', - params: { author: story.author_id.toString() }, - }); - }} - > + + + + + + + + + Author's Process + + + - - - - - - + + + + + + )} ); diff --git a/src/app/(tabs)/story/styles.ts b/src/app/(tabs)/story/styles.ts index 4967a79..202e622 100644 --- a/src/app/(tabs)/story/styles.ts +++ b/src/app/(tabs)/story/styles.ts @@ -1,5 +1,6 @@ import { StyleSheet } from 'react-native'; +import globalStyles from '../../../styles/globalStyles'; import colors from '../../../styles/colors'; const styles = StyleSheet.create({ @@ -7,6 +8,11 @@ const styles = StyleSheet.create({ paddingLeft: 24, paddingRight: 24, }, + image: { + width: '100%', + height: 200, + marginBottom: 16, + }, authorImage: { backgroundColor: '#D9D9D9', width: 21, @@ -28,9 +34,11 @@ const styles = StyleSheet.create({ flexWrap: 'wrap', borderRadius: 10, marginBottom: 16, + marginTop: 15, }, genresBorder: { backgroundColor: '#D9D9D9', + padding: 10, paddingTop: 5, paddingBottom: 5, paddingLeft: 10, @@ -39,40 +47,67 @@ const styles = StyleSheet.create({ marginRight: 8, }, genresText: { - backgroundColor: '#D9D9D9', + fontFamily: 'Manrope-Regular', + fontSize: 12, + color: 'white', }, shareButtonText: { - color: colors.white, + fontFamily: 'Manrope-Regular', + fontSize: 14, + color: 'white', + marginLeft: -5, + textDecorationLine: 'underline', }, excerpt: { + fontFamily: 'Manrope-Regular', + fontSize: 16, textAlign: 'left', - paddingVertical: 16, + color: 'black', }, story: { + fontFamily: 'Manrope-Regular', + fontSize: 16, + textAlign: 'left', + color: 'black', marginBottom: 16, }, authorProcess: { - marginBottom: 16, + fontFamily: 'Manrope-Bold', + fontSize: 20, + textAlign: 'left', + color: 'black', + marginBottom: 5, + marginTop: 10, }, process: { + fontFamily: 'Manrope-Regular', + fontSize: 16, + textAlign: 'left', + color: 'black', marginBottom: 16, }, - options: { - flex: 1, - flexDirection: 'row', - justifyContent: 'flex-end', - }, backToTopButtonText: { - fontFamily: 'Manrope-Regular', - fontSize: 12, - fontWeight: '800', + fontFamily: 'Manrope-Bold', + fontSize: 15, textAlign: 'left', color: 'black', + textDecorationLine: 'underline', }, bottomReactionContainer: { flex: 1, justifyContent: 'space-around', }, + button_style: { + width: 125, + marginBottom: 16, + borderRadius: 8, + height: 35, + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-around', + alignItems: 'center', + backgroundColor: '#EB563B', + }, }); export default styles; diff --git a/src/components/AuthorImage/AuthorImage.tsx b/src/components/AuthorImage/AuthorImage.tsx new file mode 100644 index 0000000..92b1dc8 --- /dev/null +++ b/src/components/AuthorImage/AuthorImage.tsx @@ -0,0 +1,43 @@ +import { router } from 'expo-router'; +import { Text, TouchableOpacity, View } from 'react-native'; +import { Image } from 'expo-image'; + +import styles from './styles'; +import globalStyles from '../../styles/globalStyles'; + +type AuthorImageProps = { + author_name: string; + author_id: string; + author_Uri: string; + // pressFunction: (event: GestureResponderEvent) => void; +}; + +function AuthorImage({ + author_name, + author_id, + author_Uri, // pressFunction, +}: AuthorImageProps) { + return ( + { + router.push({ + pathname: '/author', + params: { author: author_id }, + }); + }} + > + + Authors: + + + {author_name} + + + + ); +} + +export default AuthorImage; diff --git a/src/components/AuthorImage/styles.ts b/src/components/AuthorImage/styles.ts new file mode 100644 index 0000000..2309c27 --- /dev/null +++ b/src/components/AuthorImage/styles.ts @@ -0,0 +1,34 @@ +import { StyleSheet } from 'react-native'; + +import colors from '../../styles/colors'; +import globalStyles from '../../styles/globalStyles'; +export default StyleSheet.create({ + author: { + display: 'flex', + flexDirection: 'row', + gap: 10, + marginLeft: 12, + }, + authorText: { + fontFamily: 'Manrope-Regular', + fontSize: 15, + fontWeight: '400', + textAlign: 'left', + color: 'black', + }, + author_image: { + backgroundColor: '#D9D9D9', + width: 25, + height: 25, + borderRadius: 100 / 2, + }, + author_container: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'flex-start', + }, + author_text: { + fontSize: 18, + fontWeight: 'bold', + }, +}); diff --git a/src/components/FavoriteStoryButton/FavoriteStoryButton.tsx b/src/components/FavoriteStoryButton/FavoriteStoryButton.tsx index 46bf3f8..ff8113c 100644 --- a/src/components/FavoriteStoryButton/FavoriteStoryButton.tsx +++ b/src/components/FavoriteStoryButton/FavoriteStoryButton.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { TouchableOpacity } from 'react-native-gesture-handler'; import Svg, { Path } from 'react-native-svg'; @@ -18,7 +18,7 @@ export default function FavoriteStoryButton({ storyId, }: FavoriteStoryButtonProps) { const { user } = useSession(); - const { publish } = usePubSub(); + const { channels, publish } = usePubSub(); const [storyIsFavorited, setStoryIsFavorited] = useState(false); useEffect(() => { @@ -34,6 +34,15 @@ export default function FavoriteStoryButton({ }); }, [storyId]); + useEffect(() => { + const value = channels[Channel.FAVORITES][storyId]; + if (value == undefined) { + return; + } + + setStoryIsFavorited(value); + }, [channels[Channel.FAVORITES][storyId]]); + const favoriteStory = async (favorited: boolean) => { setStoryIsFavorited(favorited); @@ -46,7 +55,7 @@ export default function FavoriteStoryButton({ } }; - const renderFavoritedIcon = () => { + const renderFavoritedIcon = useMemo(() => { return ( ); - }; + }, []); - const renderNotFavoritedIcon = () => { + const renderNotFavoritedIcon = useMemo(() => { return ( ); - }; + }, []); return ( favoriteStory(!storyIsFavorited)}> - {storyIsFavorited ? renderFavoritedIcon() : renderNotFavoritedIcon()} + {storyIsFavorited ? renderFavoritedIcon : renderNotFavoritedIcon} ); } diff --git a/src/components/OptionBar/OptionBar.tsx b/src/components/OptionBar/OptionBar.tsx new file mode 100644 index 0000000..783dae6 --- /dev/null +++ b/src/components/OptionBar/OptionBar.tsx @@ -0,0 +1,50 @@ +import { Share, View } from 'react-native'; +import Icon from '../../../assets/icons'; +import ReactionPicker from '../ReactionPicker/ReactionPicker'; +import SaveStoryButton from '../SaveStoryButton/SaveStoryButton'; +import FavoriteStoryButton from '../FavoriteStoryButton/FavoriteStoryButton'; +import { TouchableOpacity } from 'react-native-gesture-handler'; +import styles from './styles'; +import { Story } from '../../queries/types'; + +type OptionBarProps = { + storyId: number; + story: Story; +}; + +function OptionBar({ storyId, story }: OptionBarProps) { + const onShare = async () => { + try { + const result = await Share.share({ + message: `Check out this story from Girls Write Now! \n${story.link}/`, + }); + if (result.action === Share.sharedAction) { + if (result.activityType) { + // shared with activity type of result.activityType + } else { + // shared + } + } else if (result.action === Share.dismissedAction) { + // dismissed + } + } catch (error) { + console.log(error); + } + }; + + return ( + + + + + + + + + + + + ); +} + +export default OptionBar; diff --git a/src/components/OptionBar/styles.ts b/src/components/OptionBar/styles.ts new file mode 100644 index 0000000..6dcb70f --- /dev/null +++ b/src/components/OptionBar/styles.ts @@ -0,0 +1,19 @@ +import { StyleSheet } from 'react-native'; + +const styles = StyleSheet.create({ + options: { + backgroundColor: 'white', + paddingTop: 16, + flex: 1, + flexDirection: 'row', + justifyContent: 'space-between', + paddingBottom: 22, + }, + right: { + alignSelf: 'flex-end', + gap: 8, + flexDirection: 'row', + }, +}); + +export default styles; diff --git a/src/components/ReactionPicker/ReactionPicker.tsx b/src/components/ReactionPicker/ReactionPicker.tsx index 637a402..71f1455 100644 --- a/src/components/ReactionPicker/ReactionPicker.tsx +++ b/src/components/ReactionPicker/ReactionPicker.tsx @@ -68,7 +68,7 @@ const ReactionPicker = ({ storyId }: ReactionPickerProps) => { key={i} onPress={() => handleReactionPress(reaction)} > - + ))} diff --git a/src/components/ReactionPicker/styles.ts b/src/components/ReactionPicker/styles.ts index 5a5a705..3d46e32 100644 --- a/src/components/ReactionPicker/styles.ts +++ b/src/components/ReactionPicker/styles.ts @@ -12,15 +12,15 @@ const styles = StyleSheet.create({ borderRadius: 20, padding: 10, alignSelf: 'center', - marginBottom: 10, }, reactionsContainer: { + flex: 1, flexDirection: 'row', gap: 5, justifyContent: 'space-between', padding: 10, position: 'absolute', // Positioning the container above the toggle button - bottom: 50, + bottom: -2, backgroundColor: '#D9D9D9', borderRadius: 20, }, diff --git a/src/components/SaveStoryButton/SaveStoryButton.tsx b/src/components/SaveStoryButton/SaveStoryButton.tsx index dbf8895..3e22084 100644 --- a/src/components/SaveStoryButton/SaveStoryButton.tsx +++ b/src/components/SaveStoryButton/SaveStoryButton.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { TouchableOpacity } from 'react-native-gesture-handler'; import Svg, { Path } from 'react-native-svg'; @@ -52,7 +52,7 @@ export default function SaveStoryButton({ } }; - const renderSavedStoryImage = () => { + const renderSavedStoryImage = useMemo(() => { return ( ); - }; + }, []); - const renderSaveStoryImage = () => { + const renderSaveStoryImage = useMemo(() => { return ( ); - }; + }, []); return ( saveStory(!storyIsSaved)}> - {storyIsSaved ? renderSavedStoryImage() : renderSaveStoryImage()} + {storyIsSaved ? renderSavedStoryImage : renderSaveStoryImage} ); } diff --git a/src/queries/stories.tsx b/src/queries/stories.tsx index a42b2a3..50574f8 100644 --- a/src/queries/stories.tsx +++ b/src/queries/stories.tsx @@ -151,32 +151,32 @@ export async function fetchNewStories(): Promise { } } -export async function fetchStoryPreviewById( - storyId: number, +export async function fetchStoryPreviewByIds( + storyIds: number[], ): Promise { - const { data, error } = await supabase.rpc('curr_story_preview_by_id', { - input_story_id: storyId, + const { data, error } = await supabase.rpc('curr_story_preview_by_ids', { + input_ids: storyIds, }); if (error) { console.log(error); throw new Error( - `An error occured when trying to fetch story preview by ID: ${error}`, + `An error occured when trying to fetch story preview by IDs: ${error}`, ); } else { return data; } } -export async function fetchStoryPreviewByIds( - storyIds: number[], +export async function fetchStoryPreviewById( + storyId: number, ): Promise { - const { data, error } = await supabase.rpc('curr_story_preview_by_ids', { - input_ids: storyIds, + const { data, error } = await supabase.rpc('curr_story_preview_by_id', { + input_story_id: storyId, }); if (error) { console.log(error); throw new Error( - `An error occured when trying to fetch story preview by IDs: ${error}`, + `An error occured when trying to fetch story preview by ID: ${error}`, ); } else { return data;