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/components/comments/CommentsContainer.tsx | 97 +++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 src/components/comments/CommentsContainer.tsx (limited to 'src/components/comments/CommentsContainer.tsx') 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; -- 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/components/comments/CommentsContainer.tsx') 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/components/comments/CommentsContainer.tsx') 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 0e86cd3c972e54cf700cca65bb2493e84056276c Mon Sep 17 00:00:00 2001 From: Ashm Walia Date: Fri, 29 Jan 2021 10:55:14 -0800 Subject: kind of works --- src/components/comments/CommentTile.tsx | 27 ++++++---- src/components/comments/CommentsContainer.tsx | 75 ++++++++++++++++++--------- src/components/notifications/Notification.tsx | 1 + src/routes/main/MainStackNavigator.tsx | 1 + src/screens/main/NotificationsScreen.tsx | 2 +- src/screens/profile/IndividualMoment.tsx | 9 ++-- src/screens/profile/MomentCommentsScreen.tsx | 5 +- 7 files changed, 77 insertions(+), 43 deletions(-) (limited to 'src/components/comments/CommentsContainer.tsx') diff --git a/src/components/comments/CommentTile.tsx b/src/components/comments/CommentTile.tsx index b631a985..347954b5 100644 --- a/src/components/comments/CommentTile.tsx +++ b/src/components/comments/CommentTile.tsx @@ -6,7 +6,12 @@ import {Alert, Animated, StyleSheet} from 'react-native'; import ClockIcon from '../../assets/icons/clock-icon-01.svg'; import {TAGG_LIGHT_BLUE} from '../../constants'; import {RectButton, TouchableOpacity} from 'react-native-gesture-handler'; -import {getTimePosted, normalize, SCREEN_WIDTH} from '../../utils'; +import { + getTimePosted, + normalize, + SCREEN_HEIGHT, + 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'; @@ -155,14 +160,16 @@ const CommentTile: React.FC = ({ {/*** Show replies if toggle state is true */} - {showReplies && ( - + {showReplies && comment_object.replies_count > 0 && ( + + + )} ); @@ -173,9 +180,9 @@ const styles = StyleSheet.create({ borderBottomWidth: 1, borderColor: 'lightgray', backgroundColor: 'white', - paddingTop: '3%', flexDirection: 'column', flex: 1, + paddingTop: '3%', marginLeft: '7%', }, swipeActions: { diff --git a/src/components/comments/CommentsContainer.tsx b/src/components/comments/CommentsContainer.tsx index d8134caf..d2f11b96 100644 --- a/src/components/comments/CommentsContainer.tsx +++ b/src/components/comments/CommentsContainer.tsx @@ -1,16 +1,17 @@ import React, {useEffect, useRef, useState} from 'react'; import {StyleSheet} from 'react-native'; -import {ScrollView} from 'react-native-gesture-handler'; +import {FlatList} from 'react-native-gesture-handler'; import {useDispatch, useSelector} from 'react-redux'; import {CommentTile} from '.'; import {getComments} from '../../services'; import {RootState} from '../../store/rootReducer'; import {CommentType, ScreenType, TypeOfComment} from '../../types'; +import {SCREEN_HEIGHT} from '../../utils'; export type CommentsContainerProps = { screenType: ScreenType; - //objectId can be either moment_id or comment_id objectId: string; + commentId?: string; setCommentsLength?: (count: number) => void; newCommentsAvailable: boolean; setNewCommentsAvailable: (value: boolean) => void; @@ -32,18 +33,21 @@ const CommentsContainer: React.FC = ({ typeOfComment, setCommentObjectInFocus, commentObjectInFocus, + commentId, }) => { const {username: loggedInUsername} = useSelector( (state: RootState) => state.user.user, ); const [commentsList, setCommentsList] = useState([]); const dispatch = useDispatch(); - const ref = useRef(null); + const ref = useRef>(null); + const [initialIndex, setInitialIndex] = useState(0); useEffect(() => { //Scroll only if a new comment and not a reply was posted const shouldScroll = () => - typeOfComment === 'Comment' && !commentObjectInFocus; + (typeOfComment === 'Comment' && !commentObjectInFocus) || + typeOfComment === 'Thread'; const loadComments = async () => { const comments = await getComments(objectId, typeOfComment === 'Thread'); setCommentsList(comments); @@ -55,9 +59,15 @@ const CommentsContainer: React.FC = ({ if (newCommentsAvailable) { loadComments(); if (shouldScroll()) { - setTimeout(() => { - ref.current?.scrollToEnd(); - }, 500); + if (commentId) { + const index = commentsList.findIndex( + (item) => item.comment_id === commentId, + ); + setInitialIndex(index); + } else { + setInitialIndex(commentsList.length - 1); + ref.current?.scrollToEnd({animated: true}); + } } } }, [ @@ -68,27 +78,44 @@ const CommentsContainer: React.FC = ({ setCommentsLength, typeOfComment, commentObjectInFocus, + commentId, + commentsList, ]); + const ITEM_HEIGHT = SCREEN_HEIGHT / 7.5; + + const renderComment = ({item}: {item: CommentType}) => ( + + ); + return ( - - {commentsList && - commentsList.map((comment: CommentType) => ( - - ))} - + keyExtractor={(item, index) => index.toString()} + decelerationRate={'fast'} + snapToAlignment={'start'} + snapToInterval={ITEM_HEIGHT} + renderItem={renderComment} + showsVerticalScrollIndicator={false} + initialScrollIndex={initialIndex} + contentContainerStyle={styles.scrollViewContent} + getItemLayout={(data, index) => ({ + length: ITEM_HEIGHT, + offset: ITEM_HEIGHT * index, + index, + })} + pagingEnabled + /> ); }; diff --git a/src/components/notifications/Notification.tsx b/src/components/notifications/Notification.tsx index 94367304..c754f941 100644 --- a/src/components/notifications/Notification.tsx +++ b/src/components/notifications/Notification.tsx @@ -122,6 +122,7 @@ const Notification: React.FC = (props) => { navigation.push('MomentCommentsScreen', { moment_id: moment.moment_id, screenType, + comment_id: notification_object?.comment_id, }); }, 500); } diff --git a/src/routes/main/MainStackNavigator.tsx b/src/routes/main/MainStackNavigator.tsx index bd838ef2..663aeaea 100644 --- a/src/routes/main/MainStackNavigator.tsx +++ b/src/routes/main/MainStackNavigator.tsx @@ -37,6 +37,7 @@ export type MainStackParams = { moment_id: string; userXId: string | undefined; screenType: ScreenType; + comment_id?: string; }; FriendsListScreen: { userXId: string | undefined; diff --git a/src/screens/main/NotificationsScreen.tsx b/src/screens/main/NotificationsScreen.tsx index 4bdee942..d9952aa8 100644 --- a/src/screens/main/NotificationsScreen.tsx +++ b/src/screens/main/NotificationsScreen.tsx @@ -70,7 +70,7 @@ const NotificationsScreen: React.FC = () => { //Called when user leaves the screen return () => resetNewNotificationFlag(); - }, [newNotificationReceived]), + }, [newNotificationReceived, dispatch, refreshNotifications]), ); // handles storing and fetching the "previously viewed" information diff --git a/src/screens/profile/IndividualMoment.tsx b/src/screens/profile/IndividualMoment.tsx index ea0c8fb6..8c1dc327 100644 --- a/src/screens/profile/IndividualMoment.tsx +++ b/src/screens/profile/IndividualMoment.tsx @@ -4,12 +4,12 @@ import {StackNavigationProp} from '@react-navigation/stack'; import React from 'react'; import {FlatList, StyleSheet, View} from 'react-native'; import {useSelector} from 'react-redux'; -import {ProfileStackParams} from 'src/routes/main/ProfileStack'; import { IndividualMomentTitleBar, MomentPostContent, MomentPostHeader, } from '../../components'; +import {MainStackParams} from '../../routes'; import {RootState} from '../../store/rootreducer'; import {MomentType} from '../../types'; import {SCREEN_HEIGHT, SCREEN_WIDTH, StatusBarHeight} from '../../utils'; @@ -17,12 +17,9 @@ import {SCREEN_HEIGHT, SCREEN_WIDTH, StatusBarHeight} from '../../utils'; /** * Individual moment view opened when user clicks on a moment tile */ -type IndividualMomentRouteProp = RouteProp< - ProfileStackParams, - 'IndividualMoment' ->; +type IndividualMomentRouteProp = RouteProp; type IndividualMomentNavigationProp = StackNavigationProp< - ProfileStackParams, + MainStackParams, 'IndividualMoment' >; interface IndividualMomentProps { diff --git a/src/screens/profile/MomentCommentsScreen.tsx b/src/screens/profile/MomentCommentsScreen.tsx index 58422f0f..5c3b8579 100644 --- a/src/screens/profile/MomentCommentsScreen.tsx +++ b/src/screens/profile/MomentCommentsScreen.tsx @@ -28,7 +28,7 @@ interface MomentCommentsScreenProps { const MomentCommentsScreen: React.FC = ({route}) => { const navigation = useNavigation(); - const {moment_id, screenType} = route.params; + const {moment_id, screenType, comment_id} = route.params; //Receives comment length from child CommentsContainer const [commentsLength, setCommentsLength] = useState(0); @@ -55,6 +55,7 @@ const MomentCommentsScreen: React.FC = ({route}) => { Date: Fri, 29 Jan 2021 11:03:55 -0800 Subject: Fix --- src/components/comments/CommentsContainer.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/components/comments/CommentsContainer.tsx') diff --git a/src/components/comments/CommentsContainer.tsx b/src/components/comments/CommentsContainer.tsx index d2f11b96..a0e94828 100644 --- a/src/components/comments/CommentsContainer.tsx +++ b/src/components/comments/CommentsContainer.tsx @@ -65,8 +65,9 @@ const CommentsContainer: React.FC = ({ ); setInitialIndex(index); } else { - setInitialIndex(commentsList.length - 1); - ref.current?.scrollToEnd({animated: true}); + setTimeout(() => { + ref.current?.scrollToEnd({animated: true}); + }, 500); } } } -- cgit v1.2.3-70-g09d2 From a3000926cb26d9ca2afba116d875653783fb622b Mon Sep 17 00:00:00 2001 From: Ashm Walia Date: Sun, 31 Jan 2021 02:25:53 -0800 Subject: Fixed --- src/components/comments/AddComment.tsx | 16 +++- src/components/comments/CommentTile.tsx | 114 +++++++++++++++----------- src/components/comments/CommentsContainer.tsx | 97 +++++++++++++++------- src/components/notifications/Notification.tsx | 80 +++++++++++++----- src/store/actions/user.ts | 18 ++++ src/store/initialStates.ts | 2 + src/store/reducers/userReducer.ts | 6 ++ src/types/types.ts | 13 ++- 8 files changed, 243 insertions(+), 103 deletions(-) (limited to 'src/components/comments/CommentsContainer.tsx') diff --git a/src/components/comments/AddComment.tsx b/src/components/comments/AddComment.tsx index 44f49748..56011f05 100644 --- a/src/components/comments/AddComment.tsx +++ b/src/components/comments/AddComment.tsx @@ -8,10 +8,11 @@ import { View, } from 'react-native'; import {TextInput, TouchableOpacity} from 'react-native-gesture-handler'; -import {useSelector} from 'react-redux'; +import {useDispatch, useSelector} from 'react-redux'; import UpArrowIcon from '../../assets/icons/up_arrow.svg'; import {TAGG_LIGHT_BLUE} from '../../constants'; import {postComment} from '../../services'; +import {updateReplyPosted} from '../../store/actions'; import {RootState} from '../../store/rootreducer'; import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; @@ -38,6 +39,7 @@ const AddComment: React.FC = ({ const [keyboardVisible, setKeyboardVisible] = React.useState(false); const {avatar} = useSelector((state: RootState) => state.user); + const dispatch = useDispatch(); const addComment = async () => { const trimmed = comment.trim(); @@ -52,6 +54,18 @@ const AddComment: React.FC = ({ if (postedComment) { setComment(''); + + //Set new reply posted object + //This helps us show the latest reply on top + //Data set is kind of stale but it works + if (isCommentInFocus) { + dispatch( + updateReplyPosted({ + comment_id: postedComment.comment_id, + parent_comment: {comment_id: objectId}, + }), + ); + } setNewCommentsAvailable(true); } }; diff --git a/src/components/comments/CommentTile.tsx b/src/components/comments/CommentTile.tsx index e775a609..be113523 100644 --- a/src/components/comments/CommentTile.tsx +++ b/src/components/comments/CommentTile.tsx @@ -1,3 +1,4 @@ +/* eslint-disable radix */ import React, {Fragment, useEffect, useRef, useState} from 'react'; import {Text, View} from 'react-native-animatable'; import {ProfilePreview} from '../profile'; @@ -6,19 +7,15 @@ import {Alert, Animated, StyleSheet} from 'react-native'; import ClockIcon from '../../assets/icons/clock-icon-01.svg'; import {TAGG_LIGHT_BLUE} from '../../constants'; import {RectButton, TouchableOpacity} from 'react-native-gesture-handler'; -import { - getTimePosted, - normalize, - SCREEN_HEIGHT, - SCREEN_WIDTH, -} from '../../utils'; +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 {deleteComment, getCommentsCount} from '../../services'; import {ERROR_FAILED_TO_DELETE_COMMENT} from '../../constants/strings'; -import {min} from 'moment'; +import {useSelector} from 'react-redux'; +import {RootState} from '../../store/rootReducer'; /** * Displays users's profile picture, comment posted by them and the time difference between now and when a comment was posted. @@ -45,10 +42,13 @@ const CommentTile: React.FC = ({ }) => { const timePosted = getTimePosted(comment_object.date_created); const [showReplies, setShowReplies] = useState(false); + const [showKeyboard, setShowKeyboard] = useState(false); const [newThreadAvailable, setNewThreadAvailable] = useState(true); const swipeRef = useRef(null); const isThread = typeOfComment === 'Thread'; + const {replyPosted} = useSelector((state: RootState) => state.user); + /** * Bubbling up, for handling a new comment in a thread. */ @@ -58,23 +58,43 @@ const CommentTile: React.FC = ({ } }, [newCommentsAvailable]); + useEffect(() => { + if (replyPosted && typeOfComment === 'Comment') { + if (replyPosted.parent_comment.comment_id === comment_object.comment_id) { + setShowReplies(true); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [replyPosted]); + /** * 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 { - setNewThreadAvailable(true); - setNewCommentsAvailable(true); - setCommentObjectInFocus(undefined); + const toggleAddComment = () => { + //Do not allow user to reply to a thread + if (!isThread) { + if (setCommentObjectInFocus) { + if (!showKeyboard) { + setCommentObjectInFocus(comment_object); + } else { + setCommentObjectInFocus(undefined); + } } + setShowKeyboard(!showKeyboard); } + }; + + const toggleReplies = async () => { + if (showReplies) { + //To update count of replies in case we deleted a reply + comment_object.replies_count = parseInt( + await getCommentsCount(comment_object.comment_id, true), + ); + } + setNewThreadAvailable(true); setShowReplies(!showReplies); }; @@ -110,7 +130,7 @@ const CommentTile: React.FC = ({ ); }; - const renderRightActions = (progress) => + const renderRightActions = (progress: Animated.AnimatedInterpolation) => canDelete ? ( {renderRightAction('Delete', '#c42634', progress)} @@ -133,48 +153,42 @@ const CommentTile: React.FC = ({ previewType={'Comment'} screenType={screenType} /> - + {comment_object.comment} {' ' + timePosted} - - {/*** Show replies text only if there are some replies present */} - {typeOfComment === 'Comment' && comment_object.replies_count > 0 && ( - - {getRepliesText()} - - - )} + {/*** 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 && comment_object.replies_count > 0 && ( - + {showReplies && ( + )} @@ -190,6 +204,7 @@ const styles = StyleSheet.create({ flexDirection: 'column', flex: 1, paddingTop: '3%', + paddingBottom: '5%', marginLeft: '7%', }, swipeActions: { @@ -204,7 +219,6 @@ const styles = StyleSheet.create({ comment: { marginBottom: '2%', marginRight: '2%', - top: -5, }, date_time: { color: 'gray', @@ -217,7 +231,7 @@ const styles = StyleSheet.create({ }, clockIconAndTime: { flexDirection: 'row', - marginBottom: '3%', + marginTop: '3%', }, flexer: { flex: 1, @@ -225,23 +239,25 @@ const styles = StyleSheet.create({ repliesTextAndIconContainer: { flexDirection: 'row', alignItems: 'center', + marginTop: '5%', + marginLeft: 56, }, repliesText: { color: TAGG_LIGHT_BLUE, fontWeight: '500', fontSize: normalize(12), - marginRight: '5%', + marginRight: '1%', }, repliesBody: { width: SCREEN_WIDTH, }, repliesDownArrow: { transform: [{rotate: '270deg'}], - marginTop: '7%', + marginTop: '1%', }, repliesUpArrow: { transform: [{rotate: '90deg'}], - marginTop: '7%', + marginTop: '1%', }, actionText: { color: 'white', diff --git a/src/components/comments/CommentsContainer.tsx b/src/components/comments/CommentsContainer.tsx index a0e94828..c72da2b7 100644 --- a/src/components/comments/CommentsContainer.tsx +++ b/src/components/comments/CommentsContainer.tsx @@ -4,6 +4,7 @@ import {FlatList} from 'react-native-gesture-handler'; import {useDispatch, useSelector} from 'react-redux'; import {CommentTile} from '.'; import {getComments} from '../../services'; +import {updateReplyPosted} from '../../store/actions'; import {RootState} from '../../store/rootReducer'; import {CommentType, ScreenType, TypeOfComment} from '../../types'; import {SCREEN_HEIGHT} from '../../utils'; @@ -41,36 +42,28 @@ const CommentsContainer: React.FC = ({ const [commentsList, setCommentsList] = useState([]); const dispatch = useDispatch(); const ref = useRef>(null); - const [initialIndex, setInitialIndex] = useState(0); useEffect(() => { - //Scroll only if a new comment and not a reply was posted - const shouldScroll = () => - (typeOfComment === 'Comment' && !commentObjectInFocus) || - typeOfComment === 'Thread'; const loadComments = async () => { - const comments = await getComments(objectId, typeOfComment === 'Thread'); - setCommentsList(comments); - if (setCommentsLength) { - setCommentsLength(comments.length); - } - setNewCommentsAvailable(false); + await getComments(objectId, typeOfComment === 'Thread').then( + (comments) => { + if (comments && subscribedToLoadComments) { + setCommentsList(comments); + if (setCommentsLength) { + setCommentsLength(comments.length); + } + setNewCommentsAvailable(false); + } + }, + ); }; + let subscribedToLoadComments = true; if (newCommentsAvailable) { loadComments(); - if (shouldScroll()) { - if (commentId) { - const index = commentsList.findIndex( - (item) => item.comment_id === commentId, - ); - setInitialIndex(index); - } else { - setTimeout(() => { - ref.current?.scrollToEnd({animated: true}); - }, 500); - } - } } + return () => { + subscribedToLoadComments = false; + }; }, [ dispatch, objectId, @@ -78,12 +71,61 @@ const CommentsContainer: React.FC = ({ setNewCommentsAvailable, setCommentsLength, typeOfComment, - commentObjectInFocus, - commentId, - commentsList, ]); - const ITEM_HEIGHT = SCREEN_HEIGHT / 7.5; + // eslint-disable-next-line no-shadow + const swapCommentTo = (commentId: string, toIndex: number) => { + const index = commentsList.findIndex( + (item) => item.comment_id === commentId, + ); + if (index > 0) { + let comments = [...commentsList]; + const temp = comments[index]; + comments[index] = comments[toIndex]; + comments[toIndex] = temp; + setCommentsList(comments); + } + }; + + useEffect(() => { + //Scroll only if a new comment and not a reply was posted + const shouldScroll = () => + typeOfComment === 'Comment' && !commentObjectInFocus; + + const performAction = () => { + if (commentId) { + swapCommentTo(commentId, 0); + } else if (shouldScroll()) { + setTimeout(() => { + ref.current?.scrollToEnd({animated: true}); + }, 500); + } + }; + if (commentsList) { + //Bring the relevant comment to top if a comment id is present else scroll if necessary + performAction(); + } + + //Clean up the reply id present in store + return () => { + if (commentId && typeOfComment === 'Thread') { + setTimeout(() => { + dispatch(updateReplyPosted(undefined)); + }, 200); + } + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [commentsList, commentId]); + + //WIP : TODO : Bring the comment in focus above the keyboard + // useEffect(() => { + // if (commentObjectInFocus && commentsList.length >= 3) { + // swapCommentTo(commentObjectInFocus.comment_id, 2); + // } + // // eslint-disable-next-line react-hooks/exhaustive-deps + // }, [commentObjectInFocus]); + + const ITEM_HEIGHT = SCREEN_HEIGHT / 7.0; const renderComment = ({item}: {item: CommentType}) => ( = ({ snapToInterval={ITEM_HEIGHT} renderItem={renderComment} showsVerticalScrollIndicator={false} - initialScrollIndex={initialIndex} contentContainerStyle={styles.scrollViewContent} getItemLayout={(data, index) => ({ length: ITEM_HEIGHT, diff --git a/src/components/notifications/Notification.tsx b/src/components/notifications/Notification.tsx index c754f941..e40153ea 100644 --- a/src/components/notifications/Notification.tsx +++ b/src/components/notifications/Notification.tsx @@ -1,18 +1,29 @@ import {useNavigation} from '@react-navigation/native'; import React, {useEffect, useState} from 'react'; -import {Image, StyleSheet, Text, View} from 'react-native'; +import {Alert, Image, StyleSheet, Text, View} from 'react-native'; import {TouchableWithoutFeedback} from 'react-native-gesture-handler'; import {useDispatch, useStore} from 'react-redux'; -import {loadImageFromURL, loadMomentThumbnail} from '../../services'; +import { + loadImageFromURL, + loadMoments, + loadMomentThumbnail, +} from '../../services'; import { acceptFriendRequest, declineFriendRequest, loadUserNotifications, + updateReplyPosted, updateUserXFriends, } from '../../store/actions'; import {RootState} from '../../store/rootReducer'; import {MomentType, NotificationType, ScreenType} from '../../types'; -import {fetchUserX, SCREEN_HEIGHT, userXInStore} from '../../utils'; +import { + fetchUserX, + getTokenOrLogout, + handleOpenSocialUrlOnBrowser, + SCREEN_HEIGHT, + userXInStore, +} from '../../utils'; import AcceptDeclineButtons from '../common/AcceptDeclineButtons'; interface NotificationProps { @@ -60,7 +71,11 @@ const Notification: React.FC = (props) => { } }; if (notification_type === 'CMT' && notification_object) { - loadMomentImage(notification_object.moment_id); + loadMomentImage( + notification_object.moment_id + ? notification_object.moment_id + : notification_object.parent_comment.moment_id, + ); return () => { mounted = false; }; @@ -84,35 +99,56 @@ const Notification: React.FC = (props) => { }); break; case 'CMT': - // find the moment we need to display + //Notification object is set to null if the comment / comment_thread / moment gets deleted + if (!notification_object) { + Alert.alert('The comment / moment was probably deleted by the user'); + break; + } + let {moment_id} = notification_object; + let {comment_id} = notification_object; + + //If this is a thread, get comment_id and moment_id from parent_comment + if (!notification_object?.moment_id) { + moment_id = notification_object?.parent_comment?.moment_id; + comment_id = notification_object?.parent_comment?.comment_id; + } + + console.log('Problem'); + + // Now find the moment we need to display let moment: MomentType | undefined = loggedInUserMoments?.find( - (m) => m.moment_id === notification_object?.moment_id, + (m) => m.moment_id === moment_id, ); let userXId; - //This needs to be done if user replies to a comment on a moment that is not user's own moment + // If moment does not belong to the logged in user, then the comment was probably a reply to logged in user's comment + // on userX's moment + // Load moments for userX if (!moment) { + let moments: MomentType[] = []; try { if (!userXInStore(state, screenType, id)) { - await fetchUserX( - dispatch, - {userId: id, username: username}, - screenType, - ); + console.log('Problem'); + const token = await getTokenOrLogout(dispatch); + moments = await loadMoments(id, token); + } else { + moments = state.userX[screenType][id].moments; } - - //Wait for data to be loaded - setTimeout(() => {}, 200); - const {moments} = state.userX[screenType][id]; - moment = moments?.find( - (m) => m.moment_id === notification_object?.moment_id, - ); - userXId = id; } catch (err) { console.log(err); } + + setTimeout(() => {}, 700); + moment = moments?.find((m) => m.moment_id === moment_id); + userXId = id; } + + console.log(moment); + //Now if moment was found, navigate to the respective moment if (moment) { + if (notification_object?.parent_comment) { + dispatch(updateReplyPosted(notification_object)); + } navigation.push('IndividualMoment', { moment, userXId: userXId, // we're only viewing our own moment here @@ -120,9 +156,9 @@ const Notification: React.FC = (props) => { }); setTimeout(() => { navigation.push('MomentCommentsScreen', { - moment_id: moment.moment_id, + moment_id: moment_id, screenType, - comment_id: notification_object?.comment_id, + comment_id: comment_id, }); }, 500); } diff --git a/src/store/actions/user.ts b/src/store/actions/user.ts index 0b1ea789..af942592 100644 --- a/src/store/actions/user.ts +++ b/src/store/actions/user.ts @@ -1,3 +1,4 @@ +import { CommentThreadType } from './../../types/types'; import {RootState} from '../rootReducer'; import {UserType} from '../../types/types'; import {loadProfileInfo, loadAvatar, loadCover} from '../../services'; @@ -9,6 +10,7 @@ import { profileCompletionStageUpdated, setIsOnboardedUser, setNewNotificationReceived, + setReplyPosted, } from '../reducers'; import {getTokenOrLogout} from '../../utils'; @@ -111,6 +113,22 @@ export const updateNewNotificationReceived = ( } }; +export const updateReplyPosted = ( + replyPosted: CommentThreadType | undefined, +): ThunkAction, RootState, unknown, Action> => async ( + dispatch, +) => { + console.log(replyPosted); + try { + dispatch({ + type: setReplyPosted.type, + payload: {replyPosted}, + }); + } catch (error) { + console.log(error); + } +}; + export const logout = (): ThunkAction< Promise, RootState, diff --git a/src/store/initialStates.ts b/src/store/initialStates.ts index 2a5b76db..8d137a5d 100644 --- a/src/store/initialStates.ts +++ b/src/store/initialStates.ts @@ -1,3 +1,4 @@ +import {CommentThreadType} from './../types/types'; import { ExploreSectionType, MomentType, @@ -44,6 +45,7 @@ export const NO_USER_DATA = { cover: '', isOnboardedUser: false, newNotificationReceived: false, + replyPosted: undefined, }; export const NO_FRIENDS_DATA = { diff --git a/src/store/reducers/userReducer.ts b/src/store/reducers/userReducer.ts index ce497677..0ef739ac 100644 --- a/src/store/reducers/userReducer.ts +++ b/src/store/reducers/userReducer.ts @@ -53,6 +53,11 @@ const userDataSlice = createSlice({ setNewNotificationReceived: (state, action) => { state.newNotificationReceived = action.payload.newNotificationReceived; }, + + setReplyPosted: (state, action) => { + console.log(action.payload.replyPosted); + state.replyPosted = action.payload.replyPosted; + }, }, }); @@ -63,5 +68,6 @@ export const { profileCompletionStageUpdated, setIsOnboardedUser, setNewNotificationReceived, + setReplyPosted, } = userDataSlice.actions; export const userDataReducer = userDataSlice.reducer; diff --git a/src/types/types.ts b/src/types/types.ts index 1d800423..1775cd5f 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -88,13 +88,20 @@ export interface MomentType { thumbnail_url: string; } -export interface CommentType { +export interface CommentBaseType { comment_id: string; comment: string; date_created: string; + commenter: ProfilePreviewType; +} + +export interface CommentType extends CommentBaseType { moment_id: string; replies_count: number; - commenter: ProfilePreviewType; +} + +export interface CommentThreadType extends CommentBaseType { + parent_comment: CommentType; } export type PreviewType = @@ -173,7 +180,7 @@ export type NotificationType = { actor: ProfilePreviewType; verbage: string; notification_type: 'DFT' | 'FRD_REQ' | 'FRD_ACPT' | 'FRD_DEC' | 'CMT'; - notification_object: CommentType | undefined; + notification_object: CommentType | CommentThreadType | undefined; timestamp: string; unread: boolean; }; -- cgit v1.2.3-70-g09d2