From 3cbfdc6026ef886b7919181b69a2f252723a6bd8 Mon Sep 17 00:00:00 2001 From: Ivan Chen Date: Wed, 27 Jan 2021 14:18:00 -0500 Subject: Squashed commit of the following: commit 9298f8a31ca25f53d7fb6a0a90af783b9b01f46c Author: Ashm Walia Date: Wed Jan 27 09:00:16 2021 -0800 Removed redundancy commit 2a93a92521d989664ebb8dfebe011d8df67ad40f Author: Ashm Walia Date: Wed Jan 27 08:58:24 2021 -0800 Sc commit 2f5f9df6dec1e905f3abf37d124ecd92d0e3a3d9 Author: Ashm Walia Date: Wed Jan 27 08:29:56 2021 -0800 Pre-final commit 85c0f668665696ba8127ee1ea436d10ec0af955f Author: Ashm Walia Date: Wed Jan 27 08:24:43 2021 -0800 Pre-final commit 755f4f540d3e759ff9ad3bc35c64b6f7fc83998b Author: Ashm Walia Date: Mon Jan 25 18:00:44 2021 -0800 Add provision to show and hide replies --- src/assets/icons/back-arrow-colored.svg | 1 + src/components/comments/AddComment.tsx | 45 ++++--- src/components/comments/CommentTile.tsx | 161 ++++++++++++++++++++++---- src/components/comments/CommentsContainer.tsx | 97 ++++++++++++++++ src/components/moments/MomentPostContent.tsx | 8 +- src/constants/api.ts | 1 + src/constants/strings.ts | 2 + src/screens/profile/MomentCommentsScreen.tsx | 81 ++++++------- src/services/CommentService.ts | 101 ++++++++++++++++ src/services/MomentServices.ts | 93 +-------------- src/services/index.ts | 1 + src/types/types.ts | 3 + 12 files changed, 413 insertions(+), 181 deletions(-) create mode 100644 src/assets/icons/back-arrow-colored.svg create mode 100644 src/components/comments/CommentsContainer.tsx create mode 100644 src/services/CommentService.ts (limited to 'src') diff --git a/src/assets/icons/back-arrow-colored.svg b/src/assets/icons/back-arrow-colored.svg new file mode 100644 index 00000000..123426d7 --- /dev/null +++ b/src/assets/icons/back-arrow-colored.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/comments/AddComment.tsx b/src/components/comments/AddComment.tsx index 24b3473c..46086e81 100644 --- a/src/components/comments/AddComment.tsx +++ b/src/components/comments/AddComment.tsx @@ -1,4 +1,4 @@ -import React, {useEffect} from 'react'; +import React, {useEffect, useRef} from 'react'; import { Image, Keyboard, @@ -11,7 +11,7 @@ import {TextInput, TouchableOpacity} from 'react-native-gesture-handler'; import {useSelector} from 'react-redux'; import UpArrowIcon from '../../assets/icons/up_arrow.svg'; import {TAGG_LIGHT_BLUE} from '../../constants'; -import {postMomentComment} from '../../services'; +import {postComment} from '../../services'; import {RootState} from '../../store/rootreducer'; import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; @@ -23,26 +23,27 @@ import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; export interface AddCommentProps { setNewCommentsAvailable: Function; - moment_id: string; + objectId: string; + placeholderText: string; + isCommentInFocus: boolean; } const AddComment: React.FC = ({ setNewCommentsAvailable, - moment_id, + objectId, + placeholderText, + isCommentInFocus, }) => { const [comment, setComment] = React.useState(''); const [keyboardVisible, setKeyboardVisible] = React.useState(false); - const { - avatar, - user: {userId}, - } = useSelector((state: RootState) => state.user); + const {avatar} = useSelector((state: RootState) => state.user); - const postComment = async () => { - const postedComment = await postMomentComment( - userId, + const addComment = async () => { + const postedComment = await postComment( comment.trim(), - moment_id, + objectId, + isCommentInFocus, ); if (postedComment) { @@ -63,6 +64,15 @@ const AddComment: React.FC = ({ return () => Keyboard.removeListener('keyboardWillHide', hideKeyboard); }, []); + const ref = useRef(null); + + //If a comment is in Focus, bring the keyboard up so user is able to type in a reply + useEffect(() => { + if (isCommentInFocus) { + ref.current?.focus(); + } + }, [isCommentInFocus]); + return ( = ({ = ({ /> - + @@ -100,6 +111,7 @@ const AddComment: React.FC = ({ ); }; + const styles = StyleSheet.create({ container: { backgroundColor: '#f7f7f7', @@ -140,6 +152,9 @@ const styles = StyleSheet.create({ marginVertical: '2%', alignSelf: 'flex-end', }, + whiteBackround: { + backgroundColor: '#fff', + }, }); export default AddComment; diff --git a/src/components/comments/CommentTile.tsx b/src/components/comments/CommentTile.tsx index 47f25a53..39605f2c 100644 --- a/src/components/comments/CommentTile.tsx +++ b/src/components/comments/CommentTile.tsx @@ -1,10 +1,14 @@ -import React from 'react'; +import React, {useState} from 'react'; import {Text, View} from 'react-native-animatable'; import {ProfilePreview} from '../profile'; -import {CommentType, ScreenType} from '../../types'; +import {CommentType, ScreenType, TypeOfComment} from '../../types'; import {StyleSheet} from 'react-native'; -import {getTimePosted} from '../../utils'; import ClockIcon from '../../assets/icons/clock-icon-01.svg'; +import {TAGG_LIGHT_BLUE} from '../../constants'; +import {TouchableOpacity} from 'react-native-gesture-handler'; +import {getTimePosted, normalize, SCREEN_WIDTH} from '../../utils'; +import Arrow from '../../assets/icons/back-arrow-colored.svg'; +import CommentsContainer from './CommentsContainer'; /** * Displays users's profile picture, comment posted by them and the time difference between now and when a comment was posted. @@ -13,54 +17,132 @@ import ClockIcon from '../../assets/icons/clock-icon-01.svg'; interface CommentTileProps { comment_object: CommentType; screenType: ScreenType; + typeOfComment: TypeOfComment; + setCommentObjectInFocus?: (comment: CommentType | undefined) => void; } const CommentTile: React.FC = ({ comment_object, screenType, + typeOfComment, + setCommentObjectInFocus, }) => { const timePosted = getTimePosted(comment_object.date_created); + const [showReplies, setShowReplies] = useState(false); + + /** + * Case : A COMMENT IS IN FOCUS && REPLY SECTION IS HIDDEN + * Bring the current comment to focus + * Case : No COMMENT IS IN FOCUS && REPLY SECTION IS SHOWN + * Unfocus comment in focus + * In any case toggle value of showReplies + */ + const toggleReplies = () => { + if (setCommentObjectInFocus) { + if (!showReplies) { + setCommentObjectInFocus(comment_object); + } else { + setCommentObjectInFocus(undefined); + } + } + setShowReplies(!showReplies); + }; + + /** + * Method to compute text to be shown for replies button + */ + const getRepliesText = () => + showReplies + ? 'Hide' + : comment_object.replies_count > 0 + ? `Replies (${comment_object.replies_count})` + : 'Replies'; + return ( - - - - {comment_object.comment} - - - {' ' + timePosted} - + <> + + + + {comment_object.comment} + + + {' ' + timePosted} + + + {/*** Show replies text only if there are some replies present */} + {typeOfComment === 'Comment' && comment_object.replies_count > 0 && ( + + {getRepliesText()} + + + )} + + - + + {/*** Show replies if toggle state is true */} + {showReplies && ( + + {}} + newCommentsAvailable={true} + typeOfComment={'Thread'} + /> + + )} + ); }; const styles = StyleSheet.create({ container: { - marginLeft: '3%', - marginRight: '3%', + marginHorizontal: '3%', borderBottomWidth: 1, borderColor: 'lightgray', marginBottom: '3%', + flexDirection: 'column', + flex: 1, + }, + moreMarginWithThread: { + marginHorizontal: '7%', }, body: { marginLeft: 56, }, comment: { - position: 'relative', - top: -5, marginBottom: '2%', + top: -5, }, date_time: { color: 'gray', + fontSize: normalize(12), }, clockIcon: { width: 12, @@ -71,6 +153,35 @@ const styles = StyleSheet.create({ flexDirection: 'row', marginBottom: '3%', }, + + flexer: { + flex: 1, + }, + + repliesTextAndIconContainer: { + flexDirection: 'row', + alignItems: 'center', + }, + + repliesText: { + color: TAGG_LIGHT_BLUE, + fontWeight: '500', + fontSize: normalize(12), + marginRight: '3%', + }, + repliesBody: { + width: SCREEN_WIDTH, + }, + + repliesDownArrow: { + transform: [{rotate: '270deg'}], + marginTop: '7%', + }, + + repliesUpArrow: { + transform: [{rotate: '90deg'}], + marginTop: '7%', + }, }); export default CommentTile; diff --git a/src/components/comments/CommentsContainer.tsx b/src/components/comments/CommentsContainer.tsx new file mode 100644 index 00000000..0a19694b --- /dev/null +++ b/src/components/comments/CommentsContainer.tsx @@ -0,0 +1,97 @@ +import React, {useRef, useEffect, useState} from 'react'; +import {StyleSheet} from 'react-native'; +import {ScrollView} from 'react-native-gesture-handler'; +import {useDispatch} from 'react-redux'; +import {CommentTile} from '.'; +import {getComments} from '../../services'; +import {CommentType, ScreenType, TypeOfComment} from '../../types'; +export type CommentsContainerProps = { + screenType: ScreenType; + + //objectId can be either moment_id or comment_id + objectId: string; + setCommentsLength?: (count: number) => void; + newCommentsAvailable: boolean; + setNewCommentsAvailable: (value: boolean) => void; + typeOfComment: TypeOfComment; + setCommentObjectInFocus?: (comment: CommentType | undefined) => void; + commentObjectInFocus?: CommentType; +}; + +/** + * Comments Container to be used for both comments and replies + */ + +const CommentsContainer: React.FC = ({ + screenType, + objectId, + setCommentsLength, + newCommentsAvailable, + setNewCommentsAvailable, + typeOfComment, + setCommentObjectInFocus, + commentObjectInFocus, +}) => { + const [commentsList, setCommentsList] = useState([]); + const dispatch = useDispatch(); + const ref = useRef(null); + + useEffect(() => { + //Scroll only if a new comment and not a reply was posted + const shouldScroll = () => + typeOfComment === 'Comment' && !commentObjectInFocus; + const loadComments = async () => { + const comments = await getComments(objectId, typeOfComment === 'Thread'); + setCommentsList(comments); + if (setCommentsLength) { + setCommentsLength(comments.length); + } + setNewCommentsAvailable(false); + }; + if (newCommentsAvailable) { + loadComments(); + if (shouldScroll()) { + setTimeout(() => { + ref.current?.scrollToEnd(); + }, 500); + } + } + }, [ + dispatch, + objectId, + newCommentsAvailable, + setNewCommentsAvailable, + setCommentsLength, + typeOfComment, + commentObjectInFocus, + ]); + + return ( + + {commentsList && + commentsList.map((comment: CommentType) => ( + + ))} + + ); +}; + +const styles = StyleSheet.create({ + scrollView: { + paddingHorizontal: 20, + }, + scrollViewContent: { + justifyContent: 'center', + }, +}); + +export default CommentsContainer; diff --git a/src/components/moments/MomentPostContent.tsx b/src/components/moments/MomentPostContent.tsx index 508b6d9f..d68ceaa3 100644 --- a/src/components/moments/MomentPostContent.tsx +++ b/src/components/moments/MomentPostContent.tsx @@ -1,6 +1,6 @@ import React, {useEffect} from 'react'; import {Image, StyleSheet, Text, View, ViewProps} from 'react-native'; -import {getMomentCommentsCount} from '../../services'; +import {getCommentsCount} from '../../services'; import {ScreenType} from '../../types'; import {getTimePosted, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; import {CommentsCount} from '../comments'; @@ -24,8 +24,12 @@ const MomentPostContent: React.FC = ({ const [comments_count, setCommentsCount] = React.useState(''); useEffect(() => { + const fetchCommentsCount = async () => { + const count = await getCommentsCount(momentId, false); + setCommentsCount(count); + }; setElapsedTime(getTimePosted(dateTime)); - getMomentCommentsCount(momentId, setCommentsCount); + fetchCommentsCount(); }, [dateTime, momentId]); return ( diff --git a/src/constants/api.ts b/src/constants/api.ts index 701070eb..32631be0 100644 --- a/src/constants/api.ts +++ b/src/constants/api.ts @@ -30,6 +30,7 @@ export const MOMENT_CATEGORY_ENDPOINT: string = API_URL + 'moment-category/'; export const NOTIFICATIONS_ENDPOINT: string = API_URL + 'notifications/'; export const DISCOVER_ENDPOINT: string = API_URL + 'discover/'; export const WAITLIST_USER_ENDPOINT: string = API_URL + 'waitlist-user/'; +export const COMMENT_THREAD_ENDPOINT: string = API_URL + 'reply/'; // Register as FCM device export const FCM_ENDPOINT: string = API_URL + 'fcm/'; diff --git a/src/constants/strings.ts b/src/constants/strings.ts index a1793658..77ded7be 100644 --- a/src/constants/strings.ts +++ b/src/constants/strings.ts @@ -1,7 +1,9 @@ + /* eslint-disable */ // Below is the regex to convert this into a csv for the Google Sheet // export const (.*) = .*?(['|"|`])(.*)\2; // replace with: $1\t$3 +export const ADD_COMMENT_TEXT = (username?: string) => username ? `Reply to ${username}` : 'Add a comment...' export const COMING_SOON_MSG = 'Creating more fun things for you, surprises coming soon 😉'; export const ERROR_AUTHENTICATION = 'An error occurred during authentication. Please login again!'; export const ERROR_CATEGORY_CREATION = 'There was a problem creating your categories. Please refresh and try again.'; diff --git a/src/screens/profile/MomentCommentsScreen.tsx b/src/screens/profile/MomentCommentsScreen.tsx index 2bceafc9..58422f0f 100644 --- a/src/screens/profile/MomentCommentsScreen.tsx +++ b/src/screens/profile/MomentCommentsScreen.tsx @@ -1,19 +1,13 @@ import {RouteProp, useNavigation} from '@react-navigation/native'; -import React, {useEffect, useRef} from 'react'; -import { - ScrollView, - StyleSheet, - Text, - TouchableOpacity, - View, -} from 'react-native'; +import React, {useState} from 'react'; +import {StyleSheet, Text, TouchableOpacity, View} from 'react-native'; import {SafeAreaView} from 'react-native-safe-area-context'; -import {useDispatch} from 'react-redux'; -import {getMomentComments} from '../..//services'; import BackIcon from '../../assets/icons/back-arrow.svg'; -import {CommentTile, TabsGradient} from '../../components'; +import {TabsGradient} from '../../components'; import {AddComment} from '../../components/'; -import {ProfileStackParams} from '../../routes/main'; +import CommentsContainer from '../../components/comments/CommentsContainer'; +import {ADD_COMMENT_TEXT} from '../../constants/strings'; +import {MainStackParams} from '../../routes/main'; import {CommentType} from '../../types'; import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; @@ -24,7 +18,7 @@ import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; */ type MomentCommentsScreenRouteProps = RouteProp< - ProfileStackParams, + MainStackParams, 'MomentCommentsScreen' >; @@ -35,25 +29,15 @@ interface MomentCommentsScreenProps { const MomentCommentsScreen: React.FC = ({route}) => { const navigation = useNavigation(); const {moment_id, screenType} = route.params; - const [commentsList, setCommentsList] = React.useState([]); + + //Receives comment length from child CommentsContainer + const [commentsLength, setCommentsLength] = useState(0); const [newCommentsAvailable, setNewCommentsAvailable] = React.useState(true); - const dispatch = useDispatch(); - const ref = useRef(null); - useEffect(() => { - const loadComments = async () => { - getMomentComments(moment_id, setCommentsList); - setNewCommentsAvailable(false); - }; - if (newCommentsAvailable) { - loadComments(); - setTimeout(() => { - ref.current?.scrollToEnd({ - animated: true, - }); - }, 500); - } - }, [dispatch, moment_id, newCommentsAvailable]); + //Keeps track of the current comments object in focus so that the application knows which comment to post a reply to + const [commentObjectInFocus, setCommentObjectInFocus] = useState< + CommentType | undefined + >(undefined); return ( @@ -66,27 +50,30 @@ const MomentCommentsScreen: React.FC = ({route}) => { }}> - - {commentsList.length + ' Comments'} - + {commentsLength + ' Comments'} - - {commentsList && - commentsList.map((comment: CommentType) => ( - - ))} - + diff --git a/src/services/CommentService.ts b/src/services/CommentService.ts new file mode 100644 index 00000000..3baf0305 --- /dev/null +++ b/src/services/CommentService.ts @@ -0,0 +1,101 @@ +import AsyncStorage from '@react-native-community/async-storage'; +import {Alert} from 'react-native'; +import {COMMENTS_ENDPOINT, COMMENT_THREAD_ENDPOINT} from '../constants'; +import {ERROR_FAILED_TO_COMMENT} from '../constants/strings'; +import {CommentType} from '../types'; + +export const getComments = async ( + objectId: string, + fetchThreads: boolean, +): Promise => { + let comments: CommentType[] = []; + try { + const token = await AsyncStorage.getItem('token'); + const endpoint = fetchThreads + ? COMMENT_THREAD_ENDPOINT + '?comment_id=' + : COMMENTS_ENDPOINT + '?moment_id='; + const response = await fetch(endpoint + objectId, { + method: 'GET', + headers: { + Authorization: 'Token ' + token, + }, + }); + const status = response.status; + if (status === 200) { + comments = await response.json(); + console.log(comments[0]); + } else { + console.log('Could not load comments'); + } + } catch (error) { + console.log('Could not load comments', error); + } + return comments; +}; + +export const postComment = async ( + comment: string, + objectId: string, + postThread: boolean, +) => { + try { + const token = await AsyncStorage.getItem('token'); + const request = new FormData(); + request.append('comment', comment); + if (postThread) { + request.append('comment_id', objectId); + } else { + request.append('moment_id', objectId); + } + const endpoint = postThread ? COMMENT_THREAD_ENDPOINT : COMMENTS_ENDPOINT; + const response = await fetch(endpoint, { + method: 'POST', + headers: { + Authorization: 'Token ' + token, + }, + body: request, + }); + if (response.status !== 200) { + throw 'server error'; + } + return await response.json(); + } catch (error) { + Alert.alert(ERROR_FAILED_TO_COMMENT); + return undefined; + } +}; + +//Get count of comments for a moment +export const getCommentsCount = async ( + objectId: string, + fetchThread: boolean, +): Promise => { + let comments_count: string = ''; + try { + const token = await AsyncStorage.getItem('token'); + const endpoint = fetchThread ? COMMENT_THREAD_ENDPOINT : COMMENTS_ENDPOINT; + const response = await fetch(endpoint + `${objectId}/`, { + method: 'GET', + headers: { + Authorization: 'Token ' + token, + }, + }); + const status = response.status; + if (status === 200) { + const response_data = await response.json(); + comments_count = response_data.count; + } else { + console.log( + 'Something went wrong! 😭', + 'Not able to retrieve comments count', + ); + } + } catch (error) { + console.log( + 'Something went wrong! 😭', + 'Not able to retrieve comments count', + error, + ); + } + return comments_count; +}; diff --git a/src/services/MomentServices.ts b/src/services/MomentServices.ts index 7bad6d4c..0110a0d6 100644 --- a/src/services/MomentServices.ts +++ b/src/services/MomentServices.ts @@ -1,100 +1,9 @@ import AsyncStorage from '@react-native-community/async-storage'; -import {Alert} from 'react-native'; import RNFetchBlob from 'rn-fetch-blob'; -import { - COMMENTS_ENDPOINT, - MOMENTS_ENDPOINT, - MOMENT_THUMBNAIL_ENDPOINT, -} from '../constants'; -import {ERROR_FAILED_TO_COMMENT} from '../constants/strings'; +import {MOMENTS_ENDPOINT, MOMENT_THUMBNAIL_ENDPOINT} from '../constants'; import {MomentType} from '../types'; import {checkImageUploadStatus} from '../utils'; -//Get all comments for a moment -export const getMomentComments = async ( - momentId: string, - callback: Function, -) => { - try { - const token = await AsyncStorage.getItem('token'); - const response = await fetch(COMMENTS_ENDPOINT + '?moment_id=' + momentId, { - method: 'GET', - headers: { - Authorization: 'Token ' + token, - }, - }); - const status = response.status; - if (status === 200) { - const comments = await response.json(); - callback(comments); - } else { - console.log('Could not load comments'); - } - } catch (error) { - console.log('Could not load comments', error); - } -}; - -export const postMomentComment = async ( - commenter: string, - comment: string, - momentId: string, -) => { - try { - const token = await AsyncStorage.getItem('token'); - const request = new FormData(); - request.append('moment_id', momentId); - request.append('commenter', commenter); - request.append('comment', comment); - const response = await fetch(COMMENTS_ENDPOINT, { - method: 'POST', - headers: { - Authorization: 'Token ' + token, - }, - body: request, - }); - if (response.status !== 200) { - throw 'server error'; - } - return await response.json(); - } catch (error) { - Alert.alert(ERROR_FAILED_TO_COMMENT); - return undefined; - } -}; - -//Get count of comments for a moment -export const getMomentCommentsCount = async ( - momentId: string, - callback: Function, -) => { - try { - const token = await AsyncStorage.getItem('token'); - const response = await fetch(COMMENTS_ENDPOINT + `${momentId}/`, { - method: 'GET', - headers: { - Authorization: 'Token ' + token, - }, - }); - const status = response.status; - if (status === 200) { - const response_data = await response.json(); - callback(response_data.count); - } else { - console.log( - 'Something went wrong! 😭', - 'Not able to retrieve comments count', - ); - } - } catch (error) { - console.log( - 'Something went wrong! 😭', - 'Not able to retrieve comments count', - error, - ); - } -}; - export const postMoment: ( fileName: string, uri: string, diff --git a/src/services/index.ts b/src/services/index.ts index 56cefddd..f558247f 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -9,3 +9,4 @@ export * from './MomentCategoryService'; export * from './NotificationService'; export * from './FCMService'; export * from './WaitlistUserService'; +export * from './CommentService'; diff --git a/src/types/types.ts b/src/types/types.ts index d9d0b56b..0c0f9d8b 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -91,6 +91,7 @@ export interface CommentType { comment: string; date_created: string; moment_id: string; + replies_count: number; commenter: ProfilePreviewType; } @@ -174,3 +175,5 @@ export type NotificationType = { timestamp: string; unread: boolean; }; + +export type TypeOfComment = 'Comment' | 'Thread'; -- cgit v1.2.3-70-g09d2 From fb69ccebb05d2ec54d85576d23989e7309349830 Mon Sep 17 00:00:00 2001 From: Ivan Chen Date: Wed, 27 Jan 2021 16:15:00 -0500 Subject: finished delete --- src/assets/ionicons/trash-outline.svg | 1 + src/components/comments/CommentTile.tsx | 115 +++++++++++++++++++------- src/components/comments/CommentsContainer.tsx | 14 ++-- src/constants/strings.ts | 2 +- src/services/CommentService.ts | 18 ++++ 5 files changed, 114 insertions(+), 36 deletions(-) create mode 100644 src/assets/ionicons/trash-outline.svg (limited to 'src') diff --git a/src/assets/ionicons/trash-outline.svg b/src/assets/ionicons/trash-outline.svg new file mode 100644 index 00000000..4920b56a --- /dev/null +++ b/src/assets/ionicons/trash-outline.svg @@ -0,0 +1 @@ +Trash \ No newline at end of file diff --git a/src/components/comments/CommentTile.tsx b/src/components/comments/CommentTile.tsx index 39605f2c..e12276a9 100644 --- a/src/components/comments/CommentTile.tsx +++ b/src/components/comments/CommentTile.tsx @@ -1,14 +1,18 @@ -import React, {useState} from 'react'; +import React, {Fragment, useRef, useState} from 'react'; import {Text, View} from 'react-native-animatable'; import {ProfilePreview} from '../profile'; import {CommentType, ScreenType, TypeOfComment} from '../../types'; -import {StyleSheet} from 'react-native'; +import {Alert, Animated, StyleSheet} from 'react-native'; import ClockIcon from '../../assets/icons/clock-icon-01.svg'; import {TAGG_LIGHT_BLUE} from '../../constants'; -import {TouchableOpacity} from 'react-native-gesture-handler'; +import {RectButton, TouchableOpacity} from 'react-native-gesture-handler'; import {getTimePosted, normalize, SCREEN_WIDTH} from '../../utils'; import Arrow from '../../assets/icons/back-arrow-colored.svg'; +import Trash from '../../assets/ionicons/trash-outline.svg'; import CommentsContainer from './CommentsContainer'; +import Swipeable from 'react-native-gesture-handler/Swipeable'; +import {deleteComment} from '../../services'; +import {ERROR_FAILED_TO_DELETE_COMMENT} from '../../constants/strings'; /** * Displays users's profile picture, comment posted by them and the time difference between now and when a comment was posted. @@ -19,6 +23,8 @@ interface CommentTileProps { screenType: ScreenType; typeOfComment: TypeOfComment; setCommentObjectInFocus?: (comment: CommentType | undefined) => void; + setNewCommentsAvailable: (available: boolean) => void; + canDelete: boolean; } const CommentTile: React.FC = ({ @@ -26,9 +32,13 @@ const CommentTile: React.FC = ({ screenType, typeOfComment, setCommentObjectInFocus, + setNewCommentsAvailable, + canDelete, }) => { const timePosted = getTimePosted(comment_object.date_created); const [showReplies, setShowReplies] = useState(false); + const swipeRef = useRef(null); + const isThread = typeOfComment === 'Thread'; /** * Case : A COMMENT IS IN FOCUS && REPLY SECTION IS HIDDEN @@ -58,13 +68,46 @@ const CommentTile: React.FC = ({ ? `Replies (${comment_object.replies_count})` : 'Replies'; + const renderRightAction = (text: string, color: string, progress) => { + const pressHandler = async () => { + swipeRef.current?.close(); + const success = await deleteComment(comment_object.comment_id, isThread); + if (success) { + setNewCommentsAvailable(true); + } else { + Alert.alert(ERROR_FAILED_TO_DELETE_COMMENT); + } + }; + return ( + + + + {text} + + + ); + }; + + const renderRightActions = (progress) => + canDelete ? ( + + {renderRightAction('Delete', '#c42634', progress)} + + ) : ( + + ); + return ( - <> + + style={[styles.container, isThread ? styles.moreMarginWithThread : {}]}> = ({ {/*** Show replies if toggle state is true */} {showReplies && ( - - {}} - newCommentsAvailable={true} - typeOfComment={'Thread'} - /> - + {}} + newCommentsAvailable={true} + typeOfComment={'Thread'} + /> )} - + ); }; const styles = StyleSheet.create({ container: { - marginHorizontal: '3%', borderBottomWidth: 1, borderColor: 'lightgray', - marginBottom: '3%', + backgroundColor: 'white', + paddingTop: '3%', flexDirection: 'column', flex: 1, + marginLeft: '7%', + }, + swipeActions: { + flexDirection: 'row', }, moreMarginWithThread: { - marginHorizontal: '7%', + marginLeft: '14%', }, body: { marginLeft: 56, }, comment: { marginBottom: '2%', + marginRight: '2%', top: -5, }, date_time: { @@ -153,35 +196,47 @@ const styles = StyleSheet.create({ flexDirection: 'row', marginBottom: '3%', }, - flexer: { flex: 1, }, - repliesTextAndIconContainer: { flexDirection: 'row', alignItems: 'center', }, - repliesText: { color: TAGG_LIGHT_BLUE, fontWeight: '500', fontSize: normalize(12), - marginRight: '3%', + marginRight: '5%', }, repliesBody: { width: SCREEN_WIDTH, }, - repliesDownArrow: { transform: [{rotate: '270deg'}], marginTop: '7%', }, - repliesUpArrow: { transform: [{rotate: '90deg'}], marginTop: '7%', }, + actionText: { + color: 'white', + fontSize: normalize(12), + fontWeight: '500', + backgroundColor: 'transparent', + paddingHorizontal: '5%', + marginTop: '5%', + }, + rightAction: { + alignItems: 'center', + flex: 1, + justifyContent: 'center', + flexDirection: 'column', + }, + swipableContainer: { + backgroundColor: 'white', + }, }); export default CommentTile; diff --git a/src/components/comments/CommentsContainer.tsx b/src/components/comments/CommentsContainer.tsx index 0a19694b..fd4b32af 100644 --- a/src/components/comments/CommentsContainer.tsx +++ b/src/components/comments/CommentsContainer.tsx @@ -1,9 +1,10 @@ -import React, {useRef, useEffect, useState} from 'react'; +import React, {useEffect, useRef, useState} from 'react'; import {StyleSheet} from 'react-native'; import {ScrollView} from 'react-native-gesture-handler'; -import {useDispatch} from 'react-redux'; +import {useDispatch, useSelector} from 'react-redux'; import {CommentTile} from '.'; import {getComments} from '../../services'; +import {RootState} from '../../store/rootReducer'; import {CommentType, ScreenType, TypeOfComment} from '../../types'; export type CommentsContainerProps = { screenType: ScreenType; @@ -32,6 +33,9 @@ const CommentsContainer: React.FC = ({ setCommentObjectInFocus, commentObjectInFocus, }) => { + const {username: loggedInUsername} = useSelector( + (state: RootState) => state.user.user, + ); const [commentsList, setCommentsList] = useState([]); const dispatch = useDispatch(); const ref = useRef(null); @@ -79,6 +83,8 @@ const CommentsContainer: React.FC = ({ screenType={screenType} typeOfComment={typeOfComment} setCommentObjectInFocus={setCommentObjectInFocus} + setNewCommentsAvailable={setNewCommentsAvailable} + canDelete={comment.commenter.username === loggedInUsername} /> ))} @@ -86,9 +92,7 @@ const CommentsContainer: React.FC = ({ }; const styles = StyleSheet.create({ - scrollView: { - paddingHorizontal: 20, - }, + scrollView: {}, scrollViewContent: { justifyContent: 'center', }, diff --git a/src/constants/strings.ts b/src/constants/strings.ts index 77ded7be..d3c81622 100644 --- a/src/constants/strings.ts +++ b/src/constants/strings.ts @@ -1,4 +1,3 @@ - /* eslint-disable */ // Below is the regex to convert this into a csv for the Google Sheet // export const (.*) = .*?(['|"|`])(.*)\2; @@ -15,6 +14,7 @@ export const ERROR_DUP_OLD_PWD = 'You may not use a previously used password'; export const ERROR_EMAIL_IN_USE = 'Email already in use, please try another one'; export const ERROR_FAILED_LOGIN_INFO = 'Login failed, please try re-entering your login information'; export const ERROR_FAILED_TO_COMMENT = 'Unable to post comment, refresh and try again!'; +export const ERROR_FAILED_TO_DELETE_COMMENT = 'Unable to delete comment, refresh and try again!'; export const ERROR_INVALID_INVITATION_CODE = 'Invitation code invalid, try again or talk to the friend that sent it 😬'; export const ERROR_INVALID_LOGIN = 'Invalid login, Please login again'; export const ERROR_INVALID_PWD_CODE = 'Looks like you have entered the wrong code, please try again'; diff --git a/src/services/CommentService.ts b/src/services/CommentService.ts index 3baf0305..a9a01d77 100644 --- a/src/services/CommentService.ts +++ b/src/services/CommentService.ts @@ -99,3 +99,21 @@ export const getCommentsCount = async ( } return comments_count; }; + +export const deleteComment = async (id: string, isThread: boolean) => { + try { + const token = await AsyncStorage.getItem('token'); + const url = isThread ? COMMENT_THREAD_ENDPOINT : COMMENTS_ENDPOINT; + const response = await fetch(url + `${id}/`, { + method: 'DELETE', + headers: { + Authorization: 'Token ' + token, + }, + }); + return response.status === 200; + } catch (error) { + console.log('Failed to delete a comment'); + console.log(error); + return false; + } +}; -- cgit v1.2.3-70-g09d2 From 56612885167c07462363aab6b606c807f58eeba5 Mon Sep 17 00:00:00 2001 From: Ivan Chen Date: Wed, 27 Jan 2021 16:27:17 -0500 Subject: fixed not refreshing --- src/components/comments/CommentTile.tsx | 19 ++++++++++++++++--- src/components/comments/CommentsContainer.tsx | 1 + src/services/CommentService.ts | 1 - 3 files changed, 17 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/components/comments/CommentTile.tsx b/src/components/comments/CommentTile.tsx index e12276a9..58d2be25 100644 --- a/src/components/comments/CommentTile.tsx +++ b/src/components/comments/CommentTile.tsx @@ -1,4 +1,4 @@ -import React, {Fragment, useRef, useState} from 'react'; +import React, {Fragment, useEffect, useRef, useState} from 'react'; import {Text, View} from 'react-native-animatable'; import {ProfilePreview} from '../profile'; import {CommentType, ScreenType, TypeOfComment} from '../../types'; @@ -23,6 +23,7 @@ interface CommentTileProps { screenType: ScreenType; typeOfComment: TypeOfComment; setCommentObjectInFocus?: (comment: CommentType | undefined) => void; + newCommentsAvailable: boolean; setNewCommentsAvailable: (available: boolean) => void; canDelete: boolean; } @@ -32,14 +33,25 @@ const CommentTile: React.FC = ({ screenType, typeOfComment, setCommentObjectInFocus, + newCommentsAvailable, setNewCommentsAvailable, canDelete, }) => { const timePosted = getTimePosted(comment_object.date_created); const [showReplies, setShowReplies] = useState(false); + const [newThreadAvailable, setNewThreadAvailable] = useState(true); const swipeRef = useRef(null); const isThread = typeOfComment === 'Thread'; + /** + * Bubbling up, for handling a new comment in a thread. + */ + useEffect(() => { + if (newCommentsAvailable) { + setNewThreadAvailable(true); + } + }, [newCommentsAvailable]); + /** * Case : A COMMENT IS IN FOCUS && REPLY SECTION IS HIDDEN * Bring the current comment to focus @@ -52,6 +64,7 @@ const CommentTile: React.FC = ({ if (!showReplies) { setCommentObjectInFocus(comment_object); } else { + setNewThreadAvailable(true); setCommentObjectInFocus(undefined); } } @@ -150,8 +163,8 @@ const CommentTile: React.FC = ({ {}} - newCommentsAvailable={true} + setNewCommentsAvailable={setNewThreadAvailable} + newCommentsAvailable={newThreadAvailable} typeOfComment={'Thread'} /> )} diff --git a/src/components/comments/CommentsContainer.tsx b/src/components/comments/CommentsContainer.tsx index fd4b32af..d8134caf 100644 --- a/src/components/comments/CommentsContainer.tsx +++ b/src/components/comments/CommentsContainer.tsx @@ -83,6 +83,7 @@ const CommentsContainer: React.FC = ({ screenType={screenType} typeOfComment={typeOfComment} setCommentObjectInFocus={setCommentObjectInFocus} + newCommentsAvailable={newCommentsAvailable} setNewCommentsAvailable={setNewCommentsAvailable} canDelete={comment.commenter.username === loggedInUsername} /> diff --git a/src/services/CommentService.ts b/src/services/CommentService.ts index a9a01d77..2faaa8db 100644 --- a/src/services/CommentService.ts +++ b/src/services/CommentService.ts @@ -23,7 +23,6 @@ export const getComments = async ( const status = response.status; if (status === 200) { comments = await response.json(); - console.log(comments[0]); } else { console.log('Could not load comments'); } -- cgit v1.2.3-70-g09d2 From 01b31b3f54ed58093df8e3f98e95268c0ace5f8a Mon Sep 17 00:00:00 2001 From: Ivan Chen Date: Wed, 27 Jan 2021 17:03:38 -0500 Subject: fixed bug to update replies count --- src/components/comments/CommentTile.tsx | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/components/comments/CommentTile.tsx b/src/components/comments/CommentTile.tsx index 58d2be25..c6dd9fc1 100644 --- a/src/components/comments/CommentTile.tsx +++ b/src/components/comments/CommentTile.tsx @@ -65,6 +65,7 @@ const CommentTile: React.FC = ({ setCommentObjectInFocus(comment_object); } else { setNewThreadAvailable(true); + setNewCommentsAvailable(true); setCommentObjectInFocus(undefined); } } -- cgit v1.2.3-70-g09d2 From ff3d1e51a1f9d175cfe460e6a16497a6226f450f Mon Sep 17 00:00:00 2001 From: Ivan Chen Date: Wed, 27 Jan 2021 17:03:50 -0500 Subject: fixed bug to submit with empty string --- src/components/comments/AddComment.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/components/comments/AddComment.tsx b/src/components/comments/AddComment.tsx index 46086e81..44f49748 100644 --- a/src/components/comments/AddComment.tsx +++ b/src/components/comments/AddComment.tsx @@ -40,8 +40,12 @@ const AddComment: React.FC = ({ const {avatar} = useSelector((state: RootState) => state.user); const addComment = async () => { + const trimmed = comment.trim(); + if (trimmed === '') { + return; + } const postedComment = await postComment( - comment.trim(), + trimmed, objectId, isCommentInFocus, ); -- cgit v1.2.3-70-g09d2