Skip to content

Commit

Permalink
[saved] Implement saved story button (#79)
Browse files Browse the repository at this point in the history
* Implemented save story button

* Move story image to assets

* Add save story to preview card, one home screen viewing bug

* Add pubsub to sync preview and content cards

* Bug fixes

* Refactor

* Create SavedStoryButton componenet

* Remove unused imports

* Fix type errors

---------

Co-authored-by: Aditya Pawar <[email protected]>
Co-authored-by: Aditya Pawar <[email protected]>
  • Loading branch information
3 people committed Apr 18, 2024
1 parent 860d346 commit b9865bc
Show file tree
Hide file tree
Showing 18 changed files with 209 additions and 40 deletions.
File renamed without changes
Binary file added assets/saved_story.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@
"react-native-vector-icons": "^10.0.2",
"react-scroll-to-top": "^3.0.0",
"use-debounce": "^10.0.0",
"validator": "^13.11.0"
"validator": "^13.11.0",
"expo-image": "~1.3.5"
},
"devDependencies": {
"@babel/core": "^7.20.0",
Expand Down
1 change: 1 addition & 0 deletions src/app/(tabs)/author/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ function AuthorScreen() {
{authorStoryPreview?.map(story => (
<PreviewCard
key={story.title}
storyId={story.id}
title={story.title}
image={story.featured_media}
author={story.author_name}
Expand Down
1 change: 1 addition & 0 deletions src/app/(tabs)/genre/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ function GenreScreen() {
renderItem={({ item }) => (
<PreviewCard
key={item.id}
storyId={item.id}
tags={item.genre_medium.concat(item.tone).concat(item.topic)}
author={item.author_name}
image={item.featured_media}
Expand Down
3 changes: 3 additions & 0 deletions src/app/(tabs)/home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ function HomeScreen() {
{featuredStories.map(story => (
<PreviewCard
key={story.id}
storyId={story.id}
title={story.title}
image={story.featured_media}
author={story.author_name}
Expand Down Expand Up @@ -132,6 +133,7 @@ function HomeScreen() {
title={story.title}
author={story.author_name}
authorImage={story.author_image}
storyId={story.id}
pressFunction={() =>
router.push({
pathname: '/story',
Expand Down Expand Up @@ -161,6 +163,7 @@ function HomeScreen() {
title={story.title}
author={story.author_name}
authorImage={story.author_image}
storyId={story.id}
pressFunction={() =>
router.push({
pathname: '/story',
Expand Down
4 changes: 3 additions & 1 deletion src/app/(tabs)/search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ function SearchScreen() {
{recentlyViewed.map(item => (
<PreviewCard
key={item.title}
storyId={item.id}
title={item.title}
image={item.featured_media}
author={item.author_name}
Expand Down Expand Up @@ -395,7 +396,8 @@ function SearchScreen() {
contentContainerStyle={styles.contentCotainerStories}
renderItem={({ item }) => (
<PreviewCard
key={item.title}
key={item.id}
storyId={item.id}
title={item.title}
image={item.featured_media}
author={item.author_name}
Expand Down
13 changes: 8 additions & 5 deletions src/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@ import { Stack } from 'expo-router';
import { SafeAreaProvider } from 'react-native-safe-area-context';

import { AuthContextProvider } from '../utils/AuthContext';
import { BooleanPubSubProvider } from '../utils/PubSubContext';
import ToastComponent from '../components/Toast/Toast';
import { Keyboard, TouchableWithoutFeedback } from 'react-native';

function StackLayout() {
return (
<SafeAreaProvider>
<AuthContextProvider>
<Stack>
<Stack.Screen name="index" options={{ headerShown: false }} />
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="auth" options={{ headerShown: false }} />
</Stack>
<BooleanPubSubProvider>
<Stack>
<Stack.Screen name="index" options={{ headerShown: false }} />
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="auth" options={{ headerShown: false }} />
</Stack>
</BooleanPubSubProvider>
</AuthContextProvider>
<ToastComponent />
</SafeAreaProvider>
Expand Down
4 changes: 1 addition & 3 deletions src/components/AuthorCard/AuthorCard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { Image, Pressable, Text, View } from 'react-native';

import { Image, Text, View } from 'react-native';
import styles from './styles';
import globalStyles from '../../styles/globalStyles';

type AuthorCardProps = {
name: string;
Expand Down
16 changes: 6 additions & 10 deletions src/components/ContentCard/ContentCard.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import {
GestureResponderEvent,
Image,
Pressable,
Text,
View,
TouchableOpacity,
} from 'react-native';
import { Image } from 'expo-image';

import styles from './styles';
import globalStyles from '../../styles/globalStyles';
import Emoji from 'react-native-emoji';
import SaveStoryButton from '../SaveStoryButton/SaveStoryButton';

type ContentCardProps = {
title: string;
author: string;
image: string;
authorImage: string;
storyId: number;
pressFunction: (event: GestureResponderEvent) => void;
};

Expand All @@ -24,12 +26,9 @@ function ContentCard({
author,
image,
authorImage,
storyId,
pressFunction,
}: ContentCardProps) {
const saveStory = () => {
console.log("testing '+' icon does something for story " + title);
};

return (
<Pressable onPress={pressFunction}>
<View style={styles.contentCard}>
Expand Down Expand Up @@ -76,11 +75,8 @@ function ContentCard({
</Text>
</View>
</View>
<TouchableOpacity onPress={() => saveStory()}>
<Image
style={styles.saveStoryImage}
source={require('./savedStoriesIcon.png')}
/>
<TouchableOpacity>
<SaveStoryButton storyId={storyId} />
</TouchableOpacity>
</View>
</View>
Expand Down
16 changes: 6 additions & 10 deletions src/components/PreviewCard/PreviewCard.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
import * as cheerio from 'cheerio';
import {
GestureResponderEvent,
Image,
Pressable,
Text,
TouchableOpacity,
View,
} from 'react-native';
import Emoji from 'react-native-emoji';
import { Image } from 'expo-image';

import styles from './styles';
import globalStyles from '../../styles/globalStyles';
import SaveStoryButton from '../SaveStoryButton/SaveStoryButton';

const placeholderImage =
'https://gwn-uploads.s3.amazonaws.com/wp-content/uploads/2021/10/10120952/Girls-Write-Now-logo-avatar.png';

type PreviewCardProps = {
title: string;
image: string;
storyId: number;
author: string;
authorImage: string;
excerpt: { html: string };
Expand All @@ -28,28 +30,22 @@ type PreviewCardProps = {
function PreviewCard({
title,
image,
storyId,
author,
authorImage,
excerpt,
tags,
pressFunction,
}: PreviewCardProps) {
const saveStory = () => {
console.log("testing '+' icon does something for story " + title);
};

return (
<Pressable onPress={pressFunction}>
<View style={styles.card}>
<View style={styles.titleContainer}>
<Text numberOfLines={1} style={[globalStyles.h3, styles.title]}>
{title}
</Text>
<TouchableOpacity onPress={() => saveStory()}>
<Image
style={{ width: 30, height: 30 }}
source={require('./savedStoriesIcon.png')}
/>
<TouchableOpacity>
<SaveStoryButton storyId={storyId} />
</TouchableOpacity>
</View>
<View style={styles.body}>
Expand Down
Binary file removed src/components/PreviewCard/savedStoriesIcon.png
Binary file not shown.
2 changes: 2 additions & 0 deletions src/components/PreviewCard/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@ const styles = StyleSheet.create({
paddingTop: 16,
paddingLeft: 12,
paddingRight: 12,
paddingBottom: 8,
borderBottomColor: '#EBEBEB',
borderBottomWidth: StyleSheet.hairlineWidth,
flexDirection: 'row',
flexGrow: 1,
justifyContent: 'space-between',
},
tag: {
Expand Down
64 changes: 64 additions & 0 deletions src/components/SaveStoryButton/SaveStoryButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { useEffect, useState } from 'react';
import {
addUserStoryToReadingList,
deleteUserStoryToReadingList,
isStoryInReadingList,
} from '../../queries/savedStories';
import { usePubSub } from '../../utils/PubSubContext';
import { useSession } from '../../utils/AuthContext';
import { Image } from 'expo-image';
import { TouchableOpacity } from 'react-native-gesture-handler';

type SaveStoryButtonProps = {
storyId: number;
};

const saveStoryImage = require('../../../assets/save_story.png');
const savedStoryImage = require('../../../assets/saved_story.png');

export default function SaveStoryButton({ storyId }: SaveStoryButtonProps) {
const { user } = useSession();
const [storyIsSaved, setStoryIsSaved] = useState(false);
const { channels, initializeChannel, publish } = usePubSub();

useEffect(() => {
isStoryInReadingList(storyId, user?.id).then(storyInReadingList => {
setStoryIsSaved(storyInReadingList);
initializeChannel(storyId);
});
}, [storyId]);

useEffect(() => {
// if another card updates this story, update it here also
if (typeof channels[storyId] !== 'undefined') {
setStoryIsSaved(channels[storyId] ?? false);
}
}, [channels[storyId]]);

useEffect(() => {
isStoryInReadingList(storyId, user?.id).then(storyInReadingList =>
setStoryIsSaved(storyInReadingList),
);
}, [storyId]);

const saveStory = async (saved: boolean) => {
setStoryIsSaved(saved);
publish(storyId, saved); // update other cards with this story

if (saved) {
await addUserStoryToReadingList(user?.id, storyId);
} else {
await deleteUserStoryToReadingList(user?.id, storyId);
}
};

return (
<TouchableOpacity onPress={() => saveStory(!storyIsSaved)}>
{storyIsSaved ? (
<Image style={{ width: 30, height: 30 }} source={savedStoryImage} />
) : (
<Image style={{ width: 30, height: 30 }} source={saveStoryImage} />
)}
</TouchableOpacity>
);
}
2 changes: 1 addition & 1 deletion src/queries/reactions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export async function fetchAllReactionsToStory(
`An error occured when trying to fetch reactions to a story', ${error}`,
);
} else {
return data;
return data as Reactions[];
}
}

Expand Down
Loading

0 comments on commit b9865bc

Please sign in to comment.