aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/comments/AddComment.tsx16
-rw-r--r--src/components/comments/CommentTile.tsx114
-rw-r--r--src/components/comments/CommentsContainer.tsx97
-rw-r--r--src/components/notifications/Notification.tsx80
-rw-r--r--src/store/actions/user.ts18
-rw-r--r--src/store/initialStates.ts2
-rw-r--r--src/store/reducers/userReducer.ts6
-rw-r--r--src/types/types.ts13
8 files changed, 243 insertions, 103 deletions
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<AddCommentProps> = ({
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<AddCommentProps> = ({
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<CommentTileProps> = ({
}) => {
const timePosted = getTimePosted(comment_object.date_created);
const [showReplies, setShowReplies] = useState<boolean>(false);
+ const [showKeyboard, setShowKeyboard] = useState<boolean>(false);
const [newThreadAvailable, setNewThreadAvailable] = useState(true);
const swipeRef = useRef<Swipeable>(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<CommentTileProps> = ({
}
}, [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<CommentTileProps> = ({
);
};
- const renderRightActions = (progress) =>
+ const renderRightActions = (progress: Animated.AnimatedInterpolation) =>
canDelete ? (
<View style={styles.swipeActions}>
{renderRightAction('Delete', '#c42634', progress)}
@@ -133,48 +153,42 @@ const CommentTile: React.FC<CommentTileProps> = ({
previewType={'Comment'}
screenType={screenType}
/>
- <TouchableOpacity style={styles.body} onPress={toggleReplies}>
+ <TouchableOpacity style={styles.body} onPress={toggleAddComment}>
<Text style={styles.comment}>{comment_object.comment}</Text>
<View style={styles.clockIconAndTime}>
<ClockIcon style={styles.clockIcon} />
<Text style={styles.date_time}>{' ' + timePosted}</Text>
<View style={styles.flexer} />
-
- {/*** Show replies text only if there are some replies present */}
- {typeOfComment === 'Comment' && comment_object.replies_count > 0 && (
- <View style={styles.repliesTextAndIconContainer}>
- <Text style={styles.repliesText}>{getRepliesText()}</Text>
- <Arrow
- width={12}
- height={11}
- color={TAGG_LIGHT_BLUE}
- style={
- !showReplies
- ? styles.repliesDownArrow
- : styles.repliesUpArrow
- }
- />
- </View>
- )}
</View>
</TouchableOpacity>
+ {/*** Show replies text only if there are some replies present */}
+ {typeOfComment === 'Comment' && comment_object.replies_count > 0 && (
+ <TouchableOpacity
+ style={styles.repliesTextAndIconContainer}
+ onPress={toggleReplies}>
+ <Text style={styles.repliesText}>{getRepliesText()}</Text>
+ <Arrow
+ width={12}
+ height={11}
+ color={TAGG_LIGHT_BLUE}
+ style={
+ !showReplies ? styles.repliesDownArrow : styles.repliesUpArrow
+ }
+ />
+ </TouchableOpacity>
+ )}
</View>
{/*** Show replies if toggle state is true */}
- {showReplies && comment_object.replies_count > 0 && (
- <View
- style={{
- height: Math.min(
- SCREEN_HEIGHT / 2.4,
- (SCREEN_HEIGHT / 7.5) * comment_object.replies_count,
- ),
- }}>
+ {showReplies && (
+ <View>
<CommentsContainer
objectId={comment_object.comment_id}
screenType={screenType}
setNewCommentsAvailable={setNewThreadAvailable}
newCommentsAvailable={newThreadAvailable}
typeOfComment={'Thread'}
+ commentId={replyPosted?.comment_id}
/>
</View>
)}
@@ -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<CommentsContainerProps> = ({
const [commentsList, setCommentsList] = useState<CommentType[]>([]);
const dispatch = useDispatch();
const ref = useRef<FlatList<CommentType>>(null);
- const [initialIndex, setInitialIndex] = useState<number>(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<CommentsContainerProps> = ({
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}) => (
<CommentTile
@@ -108,7 +150,6 @@ const CommentsContainer: React.FC<CommentsContainerProps> = ({
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<NotificationProps> = (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<NotificationProps> = (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<NotificationProps> = (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<Promise<void>, RootState, unknown, Action<string>> => async (
+ dispatch,
+) => {
+ console.log(replyPosted);
+ try {
+ dispatch({
+ type: setReplyPosted.type,
+ payload: {replyPosted},
+ });
+ } catch (error) {
+ console.log(error);
+ }
+};
+
export const logout = (): ThunkAction<
Promise<void>,
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: <string | null>'',
isOnboardedUser: false,
newNotificationReceived: false,
+ replyPosted: <CommentThreadType | undefined>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;
};