From d0237cbb61e5c4d77c7b0cefc50891639646ee91 Mon Sep 17 00:00:00 2001
From: Ashm Walia <40498934+ashmgarv@users.noreply.github.com>
Date: Thu, 22 Oct 2020 15:34:21 -0700
Subject: [TMA 236] Comments PR (#64)
* Added comments count and retrieve comments
* Working draft
* The one before cleanup
* Finally
* Added time icon and major refactoring
* Small fix for social media taggs
* Addressed review comments
---
src/assets/icons/clock-icon-01.svg | 1 +
src/assets/icons/moment-comment-icon.svg | 1 +
src/components/comments/AddComment.tsx | 103 ++++++++++++
src/components/comments/CommentTile.tsx | 71 ++++++++
src/components/comments/CommentsCount.tsx | 57 +++++++
src/components/comments/index.ts | 3 +
src/components/index.ts | 2 +
src/components/moments/CaptionScreenHeader.tsx | 37 +++++
src/components/moments/Moment.tsx | 126 +++++++++++++++
src/components/moments/MomentTile.tsx | 33 ++++
src/components/moments/index.ts | 2 +
src/components/profile/CaptionScreenHeader.tsx | 37 -----
src/components/profile/Content.tsx | 5 +-
src/components/profile/Moment.tsx | 126 ---------------
src/components/profile/MomentTile.tsx | 33 ----
src/components/profile/ProfilePreview.tsx | 216 +++++++++++++++++++++++++
src/components/profile/index.ts | 3 +-
src/components/search/SearchResult.tsx | 172 --------------------
src/components/search/SearchResults.tsx | 5 +-
src/constants/api.ts | 1 +
src/routes/profile/Profile.tsx | 26 +++
src/routes/profile/ProfileStack.tsx | 7 +
src/screens/profile/IndividualMoment.tsx | 63 ++++----
src/screens/profile/MomentCommentsScreen.tsx | 133 +++++++++++++++
src/screens/profile/SocialMediaTaggs.tsx | 3 +-
src/screens/profile/index.ts | 1 +
src/services/MomentServices.ts | 98 +++++++++++
src/services/index.ts | 1 +
src/types/types.ts | 8 +
src/utils/index.ts | 1 +
src/utils/moments.ts | 33 ++++
31 files changed, 1002 insertions(+), 406 deletions(-)
create mode 100644 src/assets/icons/clock-icon-01.svg
create mode 100644 src/assets/icons/moment-comment-icon.svg
create mode 100644 src/components/comments/AddComment.tsx
create mode 100644 src/components/comments/CommentTile.tsx
create mode 100644 src/components/comments/CommentsCount.tsx
create mode 100644 src/components/comments/index.ts
create mode 100644 src/components/moments/CaptionScreenHeader.tsx
create mode 100644 src/components/moments/Moment.tsx
create mode 100644 src/components/moments/MomentTile.tsx
create mode 100644 src/components/moments/index.ts
delete mode 100644 src/components/profile/CaptionScreenHeader.tsx
delete mode 100644 src/components/profile/Moment.tsx
delete mode 100644 src/components/profile/MomentTile.tsx
create mode 100644 src/components/profile/ProfilePreview.tsx
delete mode 100644 src/components/search/SearchResult.tsx
create mode 100644 src/screens/profile/MomentCommentsScreen.tsx
create mode 100644 src/services/MomentServices.ts
create mode 100644 src/utils/moments.ts
(limited to 'src')
diff --git a/src/assets/icons/clock-icon-01.svg b/src/assets/icons/clock-icon-01.svg
new file mode 100644
index 00000000..8e90a983
--- /dev/null
+++ b/src/assets/icons/clock-icon-01.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/moment-comment-icon.svg b/src/assets/icons/moment-comment-icon.svg
new file mode 100644
index 00000000..6b105b72
--- /dev/null
+++ b/src/assets/icons/moment-comment-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/components/comments/AddComment.tsx b/src/components/comments/AddComment.tsx
new file mode 100644
index 00000000..65c0b066
--- /dev/null
+++ b/src/components/comments/AddComment.tsx
@@ -0,0 +1,103 @@
+import * as React from 'react';
+import {Image, StyleSheet, TextInput, View} from 'react-native';
+import AsyncStorage from '@react-native-community/async-storage';
+import {AuthContext} from '../../routes';
+import {TaggBigInput} from '../onboarding';
+import {postMomentComment} from '../../services';
+
+/**
+ * This file provides the add comment view for a user.
+ * Displays the logged in user's profile picture to the left and then provides space to add a comment.
+ * Comment is posted when enter is pressed as requested by product team.
+ */
+
+export interface AddCommentProps {
+ setNewCommentsAvailable: Function;
+ moment_id: string;
+}
+
+const AddComment: React.FC = ({
+ setNewCommentsAvailable,
+ moment_id,
+}) => {
+ const [comment, setComment] = React.useState('');
+ const {
+ avatar,
+ user: {userId, username},
+ logout,
+ } = React.useContext(AuthContext);
+
+ const handleCommentUpdate = (comment: string) => {
+ setComment(comment);
+ };
+
+ const postComment = async () => {
+ try {
+ const token = await AsyncStorage.getItem('token');
+ if (!token) {
+ logout();
+ return;
+ }
+ const postedComment = await postMomentComment(
+ userId,
+ comment,
+ moment_id,
+ token,
+ );
+
+ if (postedComment) {
+ //Set the current comment to en empty string if the comment was posted successfully.
+ handleCommentUpdate('');
+
+ //Indicate the MomentCommentsScreen that it needs to download the new comments again
+ setNewCommentsAvailable(true);
+ }
+ } catch (err) {
+ console.log('Error while posting comment!');
+ }
+ };
+
+ return (
+
+
+
+
+ );
+};
+const styles = StyleSheet.create({
+ container: {flexDirection: 'row'},
+ text: {
+ position: 'relative',
+ right: '18%',
+ backgroundColor: 'white',
+ width: '70%',
+ paddingLeft: '2%',
+ paddingRight: '2%',
+ paddingBottom: '1%',
+ paddingTop: '1%',
+ height: 60,
+ },
+ avatar: {
+ height: 40,
+ width: 40,
+ borderRadius: 30,
+ marginRight: 15,
+ },
+});
+
+export default AddComment;
diff --git a/src/components/comments/CommentTile.tsx b/src/components/comments/CommentTile.tsx
new file mode 100644
index 00000000..02840d47
--- /dev/null
+++ b/src/components/comments/CommentTile.tsx
@@ -0,0 +1,71 @@
+import React from 'react';
+import {Text, View} from 'react-native-animatable';
+import {ProfilePreview} from '../profile';
+import {CommentType} from '../../types';
+import {StyleSheet} from 'react-native';
+import {getTimePosted} from '../../utils';
+import ClockIcon from '../../assets/icons/clock-icon-01.svg';
+
+/**
+ * Displays users's profile picture, comment posted by them and the time difference between now and when a comment was posted.
+ */
+
+interface CommentTileProps {
+ comment_object: CommentType;
+}
+
+const CommentTile: React.FC = ({comment_object}) => {
+ const timePosted = getTimePosted(comment_object.date_time);
+ return (
+
+
+
+ {comment_object.comment}
+
+
+ {' ' + timePosted}
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ marginLeft: '3%',
+ marginRight: '3%',
+ borderBottomWidth: 1,
+ borderColor: 'lightgray',
+ marginBottom: '3%',
+ },
+ body: {
+ marginLeft: 56,
+ },
+ comment: {
+ position: 'relative',
+ top: -5,
+ marginBottom: '2%',
+ },
+ date_time: {
+ color: 'gray',
+ },
+ clockIcon: {
+ width: 12,
+ height: 12,
+ alignSelf: 'center',
+ },
+ clockIconAndTime: {
+ flexDirection: 'row',
+ marginBottom: '3%',
+ },
+});
+
+export default CommentTile;
diff --git a/src/components/comments/CommentsCount.tsx b/src/components/comments/CommentsCount.tsx
new file mode 100644
index 00000000..74b4194c
--- /dev/null
+++ b/src/components/comments/CommentsCount.tsx
@@ -0,0 +1,57 @@
+import * as React from 'react';
+import {Text} from 'react-native-animatable';
+import {StyleSheet, TouchableOpacity} from 'react-native';
+import CommentIcon from '../../assets/icons/moment-comment-icon.svg';
+import {useNavigation} from '@react-navigation/native';
+
+/**
+ * Provides a view for the comment icon and the comment count.
+ * When the user clicks on this view, a new screen opens to display all the comments.
+ */
+
+type CommentsCountProps = {
+ comments_count: string;
+ isProfileView: boolean;
+ moment_id: string;
+};
+
+const CommentsCount: React.FC = ({
+ comments_count,
+ isProfileView,
+ moment_id,
+}) => {
+ const navigation = useNavigation();
+ const navigateToCommentsScreen = async () => {
+ navigation.navigate('MomentCommentsScreen', {
+ isProfileView: isProfileView,
+ moment_id: moment_id,
+ });
+ };
+ return (
+ <>
+ navigateToCommentsScreen()}>
+
+
+ {comments_count !== '0' ? comments_count : ''}
+
+
+ >
+ );
+};
+
+const styles = StyleSheet.create({
+ image: {
+ position: 'relative',
+ width: 21,
+ height: 21,
+ },
+
+ count: {
+ position: 'relative',
+ fontWeight: 'bold',
+ color: 'white',
+ paddingTop: '2%',
+ },
+});
+
+export default CommentsCount;
diff --git a/src/components/comments/index.ts b/src/components/comments/index.ts
new file mode 100644
index 00000000..6293f799
--- /dev/null
+++ b/src/components/comments/index.ts
@@ -0,0 +1,3 @@
+export {default as CommentsCount} from '../comments/CommentsCount';
+export {default as CommentTile} from './CommentTile';
+export {default as AddComment} from './AddComment';
diff --git a/src/components/index.ts b/src/components/index.ts
index 1b726051..46a7773f 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -3,3 +3,5 @@ export * from './onboarding';
export * from './profile';
export * from './search';
export * from './taggs';
+export * from './comments';
+export * from './moments';
diff --git a/src/components/moments/CaptionScreenHeader.tsx b/src/components/moments/CaptionScreenHeader.tsx
new file mode 100644
index 00000000..4715b4ef
--- /dev/null
+++ b/src/components/moments/CaptionScreenHeader.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+import {Text, View, StyleSheet, ViewProps} from 'react-native';
+interface CaptionScreenHeaderProps extends ViewProps {
+ title: string;
+}
+const CaptionScreenHeader: React.FC = ({
+ title,
+ style,
+}) => {
+ return (
+
+
+ {title}
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flexDirection: 'row',
+ justifyContent: 'center',
+ height: 30,
+ },
+ headerContainer: {
+ position: 'absolute',
+ left: '50%',
+ },
+ header: {
+ position: 'relative',
+ right: '50%',
+ fontSize: 20,
+ fontWeight: 'bold',
+ color: 'white',
+ },
+});
+export default CaptionScreenHeader;
diff --git a/src/components/moments/Moment.tsx b/src/components/moments/Moment.tsx
new file mode 100644
index 00000000..1ec5511e
--- /dev/null
+++ b/src/components/moments/Moment.tsx
@@ -0,0 +1,126 @@
+import {useNavigation} from '@react-navigation/native';
+import React from 'react';
+import {Alert, StyleSheet, View} from 'react-native';
+import {Text} from 'react-native-animatable';
+import {ScrollView, TouchableOpacity} from 'react-native-gesture-handler';
+import LinearGradient from 'react-native-linear-gradient';
+import PlusIcon from '../../assets/icons/plus_icon-01.svg';
+import BigPlusIcon from '../../assets/icons/plus_icon-02.svg';
+import {MOMENTS_TITLE_COLOR} from '../../constants';
+import {SCREEN_WIDTH} from '../../utils';
+import ImagePicker from 'react-native-image-crop-picker';
+import MomentTile from './MomentTile';
+import {MomentType} from 'src/types';
+
+interface MomentProps {
+ title: string;
+ images: MomentType[] | undefined;
+ isProfileView: boolean;
+}
+
+const Moment: React.FC = ({title, images, isProfileView}) => {
+ const navigation = useNavigation();
+
+ const navigateToImagePicker = () => {
+ ImagePicker.openPicker({
+ width: 580,
+ height: 580,
+ cropping: true,
+ cropperToolbarTitle: 'Upload a moment',
+ mediaType: 'photo',
+ })
+ .then((picture) => {
+ if ('path' in picture) {
+ navigation.navigate('CaptionScreen', {
+ title: title,
+ image: picture,
+ });
+ }
+ })
+ .catch((err) => {
+ Alert.alert('Unable to upload moment!');
+ });
+ };
+ return (
+
+
+ {title}
+ {!isProfileView ? (
+ navigateToImagePicker()}
+ />
+ ) : (
+
+ )}
+
+
+ {images &&
+ images.map((imageObj: MomentType) => (
+
+ ))}
+ {(images === undefined || images.length === 0) && !isProfileView && (
+ navigateToImagePicker()}>
+
+
+
+
+ Add a moment of your {title.toLowerCase()}!
+
+
+
+
+ )}
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ flexDirection: 'column',
+ backgroundColor: '#eee',
+ },
+ header: {
+ flex: 1,
+ paddingHorizontal: 10,
+ padding: 5,
+ paddingTop: 20,
+ backgroundColor: 'white',
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ },
+ titleText: {
+ fontSize: 16,
+ fontWeight: 'bold',
+ color: MOMENTS_TITLE_COLOR,
+ },
+ scrollContainer: {
+ height: SCREEN_WIDTH / 2,
+ backgroundColor: '#eee',
+ },
+ defaultImage: {
+ aspectRatio: 1,
+ height: '100%',
+ alignItems: 'center',
+ justifyContent: 'center',
+ flexDirection: 'column',
+ },
+ defaultImageText: {
+ fontSize: 20,
+ paddingTop: 20,
+ color: 'white',
+ fontWeight: 'bold',
+ width: '75%',
+ textAlign: 'center',
+ },
+});
+
+export default Moment;
diff --git a/src/components/moments/MomentTile.tsx b/src/components/moments/MomentTile.tsx
new file mode 100644
index 00000000..70b20d40
--- /dev/null
+++ b/src/components/moments/MomentTile.tsx
@@ -0,0 +1,33 @@
+import {useNavigation} from '@react-navigation/native';
+import React from 'react';
+import {StyleSheet, View, Image, TouchableOpacity} from 'react-native';
+import {MomentType} from 'src/types';
+
+interface MomentTileProps {
+ moment: MomentType;
+}
+const MomentTile: React.FC = ({moment}) => {
+ const navigation = useNavigation();
+ const {path_hash} = moment;
+ return (
+ {
+ navigation.navigate('IndividualMoment', {moment});
+ }}>
+
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ image: {
+ aspectRatio: 1,
+ height: '100%',
+ alignItems: 'center',
+ justifyContent: 'center',
+ flexDirection: 'column',
+ },
+});
+export default MomentTile;
diff --git a/src/components/moments/index.ts b/src/components/moments/index.ts
new file mode 100644
index 00000000..339e0e19
--- /dev/null
+++ b/src/components/moments/index.ts
@@ -0,0 +1,2 @@
+export {default as CaptionScreenHeader} from '../moments/CaptionScreenHeader';
+export {default as Moment} from './Moment';
diff --git a/src/components/profile/CaptionScreenHeader.tsx b/src/components/profile/CaptionScreenHeader.tsx
deleted file mode 100644
index 4715b4ef..00000000
--- a/src/components/profile/CaptionScreenHeader.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import React from 'react';
-import {Text, View, StyleSheet, ViewProps} from 'react-native';
-interface CaptionScreenHeaderProps extends ViewProps {
- title: string;
-}
-const CaptionScreenHeader: React.FC = ({
- title,
- style,
-}) => {
- return (
-
-
- {title}
-
-
- );
-};
-
-const styles = StyleSheet.create({
- container: {
- flexDirection: 'row',
- justifyContent: 'center',
- height: 30,
- },
- headerContainer: {
- position: 'absolute',
- left: '50%',
- },
- header: {
- position: 'relative',
- right: '50%',
- fontSize: 20,
- fontWeight: 'bold',
- color: 'white',
- },
-});
-export default CaptionScreenHeader;
diff --git a/src/components/profile/Content.tsx b/src/components/profile/Content.tsx
index 0bf66dc7..8f20cd8d 100644
--- a/src/components/profile/Content.tsx
+++ b/src/components/profile/Content.tsx
@@ -7,7 +7,7 @@ import {MomentType} from 'src/types';
import {defaultMoments, MOMENTS_ENDPOINT} from '../../constants';
import {SCREEN_HEIGHT} from '../../utils';
import TaggsBar from '../taggs/TaggsBar';
-import Moment from './Moment';
+import {Moment} from '../moments';
import ProfileBody from './ProfileBody';
import ProfileCutout from './ProfileCutout';
import ProfileHeader from './ProfileHeader';
@@ -45,7 +45,6 @@ const Content: React.FC = ({y, isProfileView}) => {
});
setImagesMap(map);
- console.log(map);
}, [imagesList]);
useEffect(() => {
@@ -56,7 +55,7 @@ const Content: React.FC = ({y, isProfileView}) => {
const retrieveMoments = async () => {
try {
const token = await AsyncStorage.getItem('token');
- const response = await fetch(MOMENTS_ENDPOINT + `${userId}/`, {
+ const response = await fetch(MOMENTS_ENDPOINT + '?user_id=' + userId, {
method: 'GET',
headers: {
Authorization: 'Token ' + token,
diff --git a/src/components/profile/Moment.tsx b/src/components/profile/Moment.tsx
deleted file mode 100644
index 1ec5511e..00000000
--- a/src/components/profile/Moment.tsx
+++ /dev/null
@@ -1,126 +0,0 @@
-import {useNavigation} from '@react-navigation/native';
-import React from 'react';
-import {Alert, StyleSheet, View} from 'react-native';
-import {Text} from 'react-native-animatable';
-import {ScrollView, TouchableOpacity} from 'react-native-gesture-handler';
-import LinearGradient from 'react-native-linear-gradient';
-import PlusIcon from '../../assets/icons/plus_icon-01.svg';
-import BigPlusIcon from '../../assets/icons/plus_icon-02.svg';
-import {MOMENTS_TITLE_COLOR} from '../../constants';
-import {SCREEN_WIDTH} from '../../utils';
-import ImagePicker from 'react-native-image-crop-picker';
-import MomentTile from './MomentTile';
-import {MomentType} from 'src/types';
-
-interface MomentProps {
- title: string;
- images: MomentType[] | undefined;
- isProfileView: boolean;
-}
-
-const Moment: React.FC = ({title, images, isProfileView}) => {
- const navigation = useNavigation();
-
- const navigateToImagePicker = () => {
- ImagePicker.openPicker({
- width: 580,
- height: 580,
- cropping: true,
- cropperToolbarTitle: 'Upload a moment',
- mediaType: 'photo',
- })
- .then((picture) => {
- if ('path' in picture) {
- navigation.navigate('CaptionScreen', {
- title: title,
- image: picture,
- });
- }
- })
- .catch((err) => {
- Alert.alert('Unable to upload moment!');
- });
- };
- return (
-
-
- {title}
- {!isProfileView ? (
- navigateToImagePicker()}
- />
- ) : (
-
- )}
-
-
- {images &&
- images.map((imageObj: MomentType) => (
-
- ))}
- {(images === undefined || images.length === 0) && !isProfileView && (
- navigateToImagePicker()}>
-
-
-
-
- Add a moment of your {title.toLowerCase()}!
-
-
-
-
- )}
-
-
- );
-};
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- flexDirection: 'column',
- backgroundColor: '#eee',
- },
- header: {
- flex: 1,
- paddingHorizontal: 10,
- padding: 5,
- paddingTop: 20,
- backgroundColor: 'white',
- flexDirection: 'row',
- justifyContent: 'space-between',
- alignItems: 'center',
- },
- titleText: {
- fontSize: 16,
- fontWeight: 'bold',
- color: MOMENTS_TITLE_COLOR,
- },
- scrollContainer: {
- height: SCREEN_WIDTH / 2,
- backgroundColor: '#eee',
- },
- defaultImage: {
- aspectRatio: 1,
- height: '100%',
- alignItems: 'center',
- justifyContent: 'center',
- flexDirection: 'column',
- },
- defaultImageText: {
- fontSize: 20,
- paddingTop: 20,
- color: 'white',
- fontWeight: 'bold',
- width: '75%',
- textAlign: 'center',
- },
-});
-
-export default Moment;
diff --git a/src/components/profile/MomentTile.tsx b/src/components/profile/MomentTile.tsx
deleted file mode 100644
index 70b20d40..00000000
--- a/src/components/profile/MomentTile.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import {useNavigation} from '@react-navigation/native';
-import React from 'react';
-import {StyleSheet, View, Image, TouchableOpacity} from 'react-native';
-import {MomentType} from 'src/types';
-
-interface MomentTileProps {
- moment: MomentType;
-}
-const MomentTile: React.FC = ({moment}) => {
- const navigation = useNavigation();
- const {path_hash} = moment;
- return (
- {
- navigation.navigate('IndividualMoment', {moment});
- }}>
-
-
-
-
- );
-};
-
-const styles = StyleSheet.create({
- image: {
- aspectRatio: 1,
- height: '100%',
- alignItems: 'center',
- justifyContent: 'center',
- flexDirection: 'column',
- },
-});
-export default MomentTile;
diff --git a/src/components/profile/ProfilePreview.tsx b/src/components/profile/ProfilePreview.tsx
new file mode 100644
index 00000000..c527746a
--- /dev/null
+++ b/src/components/profile/ProfilePreview.tsx
@@ -0,0 +1,216 @@
+import React, {useEffect, useState, useContext} from 'react';
+import {ProfilePreviewType} from '../../types';
+import {
+ View,
+ Text,
+ Image,
+ StyleSheet,
+ ViewProps,
+ TouchableOpacity,
+} from 'react-native';
+import {useNavigation} from '@react-navigation/native';
+import RNFetchBlob from 'rn-fetch-blob';
+import AsyncStorage from '@react-native-community/async-storage';
+import {AVATAR_PHOTO_ENDPOINT} from '../../constants';
+import {UserType} from '../../types';
+import {ProfileContext} from '../../routes/viewProfile';
+const NO_USER: UserType = {
+ userId: '',
+ username: '',
+};
+
+/**
+ * This component returns user's profile picture followed by username as a touchable component.
+ * What happens when someone clicks on this component is partly decided by the prop isComment.
+ * If isComment is true then it means that we are not displaying this tile as a part of search results.
+ * And hence we do not cache the search results.
+ * On the other hand, if isComment is false, then we should update the search cache. (This cache needs to be revamped to clear outdated results.)
+ * In either case, we load the ProfileContext with data and set the getNewMoments flag to true (Which ensures that everything that needs to be displayed on a user's profile is set).
+ * Finally, We navigate to Profile if we are on the Search Stack. Else we navigate to ProfileView.
+ */
+
+interface ProfilePreviewProps extends ViewProps {
+ profilePreview: ProfilePreviewType;
+ isComment: boolean;
+}
+const ProfilePreview: React.FC = ({
+ profilePreview: {username, first_name, last_name, id},
+ isComment,
+ style,
+}) => {
+ const navigation = useNavigation();
+ const {loadProfile, updateMoments} = useContext(ProfileContext);
+ const [avatarURI, setAvatarURI] = useState(null);
+ const [user, setUser] = useState(NO_USER);
+ useEffect(() => {
+ let mounted = true;
+ const loadAvatar = async () => {
+ try {
+ const token = await AsyncStorage.getItem('token');
+ if (!token) {
+ setUser(NO_USER);
+ return;
+ }
+ const response = await RNFetchBlob.config({
+ fileCache: true,
+ appendExt: 'jpg',
+ }).fetch('GET', AVATAR_PHOTO_ENDPOINT + `${id}/`, {
+ Authorization: 'Token ' + token,
+ });
+ const status = response.info().status;
+ if (status === 200) {
+ if (mounted) {
+ setAvatarURI(response.path());
+ }
+ return;
+ }
+ if (mounted) {
+ setAvatarURI('');
+ }
+ } catch (error) {
+ console.log(error);
+ }
+ };
+ loadAvatar();
+ return () => {
+ mounted = false;
+ };
+ }, [id]);
+
+ /**
+ * Adds a searched user to the recently searched cache if they're tapped on.
+ * Cache maintains 10 recently searched users, popping off the oldest one if
+ * needed to make space.
+ */
+ const addToRecentlyStoredAndNavigateToProfile = async () => {
+ let user: ProfilePreviewType = {
+ id,
+ username,
+ first_name,
+ last_name,
+ };
+ try {
+ if (!isComment) {
+ const jsonValue = await AsyncStorage.getItem(
+ '@recently_searched_users',
+ );
+ let recentlySearchedList =
+ jsonValue != null ? JSON.parse(jsonValue) : null;
+ if (recentlySearchedList) {
+ if (recentlySearchedList.length > 0) {
+ if (
+ recentlySearchedList.some(
+ (saved_user: ProfilePreviewType) => saved_user.id === id,
+ )
+ ) {
+ console.log('User already in recently searched.');
+ } else {
+ if (recentlySearchedList.length >= 10) {
+ recentlySearchedList.pop();
+ }
+ recentlySearchedList.unshift(user);
+ }
+ }
+ } else {
+ recentlySearchedList = [user];
+ }
+
+ try {
+ let recentlySearchedListString = JSON.stringify(recentlySearchedList);
+ await AsyncStorage.setItem(
+ '@recently_searched_users',
+ recentlySearchedListString,
+ );
+ } catch (e) {
+ console.log(e);
+ }
+ }
+
+ //Load user profile and set new moments to true, navigate to Profile
+ //Load user profile makes sure that we actually load profile of the user the logged in user want to view
+ //Set new moments to true makes sure that we download the moment for the user being viewed again.
+ loadProfile(user.id, user.username);
+ updateMoments(true);
+ if (!isComment) {
+ navigation.navigate('Profile', {
+ isProfileView: true,
+ });
+ } else {
+ navigation.navigate('ProfileView', {
+ isProfileView: true,
+ });
+ }
+ } catch (e) {
+ console.log(e);
+ }
+ };
+
+ //With @ sign if on search screen.
+ const usernameToDisplay = !isComment ? `@` + username : username;
+ const usernameStyle = isComment
+ ? styles.commentUsername
+ : styles.searchUsername;
+
+ const avatarStyle = !isComment ? styles.searchAvatar : styles.commentAvatar;
+
+ return (
+
+
+
+ {usernameToDisplay}
+ {first_name ? (
+ {first_name.concat(' ', last_name)}
+ ) : (
+ React.Fragment
+ )}
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ searchAvatar: {
+ height: 60,
+ width: 60,
+ borderRadius: 30,
+ marginRight: 15,
+ },
+ commentAvatar: {
+ height: 40,
+ width: 40,
+ borderRadius: 20,
+ marginRight: 15,
+ marginTop: '2%',
+ },
+ nameContainer: {
+ justifyContent: 'space-evenly',
+ alignSelf: 'stretch',
+ },
+ searchUsername: {
+ fontSize: 18,
+ fontWeight: '500',
+ },
+ commentUsername: {
+ fontSize: 16,
+ fontWeight: '500',
+ },
+ name: {
+ fontSize: 16,
+ color: '#333',
+ },
+});
+
+export default ProfilePreview;
diff --git a/src/components/profile/index.ts b/src/components/profile/index.ts
index e2063e26..eb65d509 100644
--- a/src/components/profile/index.ts
+++ b/src/components/profile/index.ts
@@ -1,7 +1,6 @@
export {default as Cover} from './Cover';
export {default as Content} from './Content';
-export {default as Moment} from './Moment';
export {default as ProfileCutout} from './ProfileCutout';
export {default as ProfileBody} from './ProfileBody';
export {default as ProfileHeader} from './ProfileHeader';
-export {default as CaptionScreenHeader} from './CaptionScreenHeader';
+export {default as ProfilePreview} from '../profile/ProfilePreview';
diff --git a/src/components/search/SearchResult.tsx b/src/components/search/SearchResult.tsx
deleted file mode 100644
index 04624004..00000000
--- a/src/components/search/SearchResult.tsx
+++ /dev/null
@@ -1,172 +0,0 @@
-import React, {useEffect, useState, useContext} from 'react';
-import {ProfilePreviewType} from '../../types';
-import {
- View,
- Text,
- Image,
- StyleSheet,
- ViewProps,
- TouchableOpacity,
-} from 'react-native';
-import {useNavigation} from '@react-navigation/native';
-import RNFetchBlob from 'rn-fetch-blob';
-import AsyncStorage from '@react-native-community/async-storage';
-import {AVATAR_PHOTO_ENDPOINT} from '../../constants';
-import {UserType} from '../../types';
-import {ProfileContext} from '../../routes/viewProfile';
-const NO_USER: UserType = {
- userId: '',
- username: '',
-};
-
-interface SearchResultProps extends ViewProps {
- profilePreview: ProfilePreviewType;
-}
-const SearchResult: React.FC = ({
- profilePreview: {username, first_name, last_name, id},
- style,
-}) => {
- const navigation = useNavigation();
- const {loadProfile, updateMoments} = useContext(ProfileContext);
- const [avatarURI, setAvatarURI] = useState(null);
- const [user, setUser] = useState(NO_USER);
- useEffect(() => {
- let mounted = true;
- const loadAvatar = async () => {
- try {
- const token = await AsyncStorage.getItem('token');
- if (!token) {
- setUser(NO_USER);
- return;
- }
- const response = await RNFetchBlob.config({
- fileCache: true,
- appendExt: 'jpg',
- }).fetch('GET', AVATAR_PHOTO_ENDPOINT + `${id}/`, {
- Authorization: 'Token ' + token,
- });
- const status = response.info().status;
- if (status === 200) {
- if (mounted) {
- setAvatarURI(response.path());
- }
- return;
- }
- if (mounted) {
- setAvatarURI('');
- }
- } catch (error) {
- console.log(error);
- }
- };
- loadAvatar();
- return () => {
- mounted = false;
- };
- }, [id]);
-
- /**
- * Adds a searched user to the recently searched cache if they're tapped on.
- * Cache maintains 10 recently searched users, popping off the oldest one if
- * needed to make space.
- */
- const addToRecentlyStoredAndNavigateToProfile = async () => {
- let user: ProfilePreviewType = {
- id,
- username,
- first_name,
- last_name,
- };
- try {
- const jsonValue = await AsyncStorage.getItem('@recently_searched_users');
- let recentlySearchedList =
- jsonValue != null ? JSON.parse(jsonValue) : null;
- if (recentlySearchedList) {
- if (recentlySearchedList.length > 0) {
- if (
- recentlySearchedList.some(
- (saved_user: ProfilePreviewType) => saved_user.id === id,
- )
- ) {
- console.log('User already in recently searched.');
- } else {
- if (recentlySearchedList.length >= 10) {
- recentlySearchedList.pop();
- }
- recentlySearchedList.unshift(user);
- }
- }
- } else {
- recentlySearchedList = [user];
- }
-
- //Load user profile and set new moments to true, navigate to Profile
- //Load user profile makes sure that we actually load profile of the user the logged in user want to view
- //Set new moments to true makes sure that we download the moment for the user being viewed again.
- //Not sure if we should make this call before caching the search results ??
- loadProfile(user.id, user.username);
- updateMoments(true);
- navigation.navigate('Profile', {
- isProfileView: true,
- });
-
- try {
- let recentlySearchedListString = JSON.stringify(recentlySearchedList);
- await AsyncStorage.setItem(
- '@recently_searched_users',
- recentlySearchedListString,
- );
- } catch (e) {
- console.log(e);
- }
- } catch (e) {
- console.log(e);
- }
- };
-
- return (
-
-
-
- @{username}
- {first_name.concat(' ', last_name)}
-
-
- );
-};
-
-const styles = StyleSheet.create({
- container: {
- flexDirection: 'row',
- alignItems: 'center',
- },
- avatar: {
- height: 60,
- width: 60,
- borderRadius: 30,
- marginRight: 15,
- },
- nameContainer: {
- justifyContent: 'space-evenly',
- alignSelf: 'stretch',
- },
- username: {
- fontSize: 18,
- fontWeight: '500',
- },
- name: {
- fontSize: 16,
- color: '#333',
- },
-});
-
-export default SearchResult;
diff --git a/src/components/search/SearchResults.tsx b/src/components/search/SearchResults.tsx
index 16bff818..57db4167 100644
--- a/src/components/search/SearchResults.tsx
+++ b/src/components/search/SearchResults.tsx
@@ -1,6 +1,6 @@
import React from 'react';
import {ProfilePreviewType} from '../../types';
-import SearchResult from './SearchResult';
+import ProfilePreview from '../profile/ProfilePreview';
import {StyleSheet, View} from 'react-native';
interface SearchResultsProps {
results: Array;
@@ -9,10 +9,11 @@ const SearchResults: React.FC = ({results}) => {
return (
{results.map((profilePreview) => (
-
))}
diff --git a/src/constants/api.ts b/src/constants/api.ts
index 3d7bd017..181247dd 100644
--- a/src/constants/api.ts
+++ b/src/constants/api.ts
@@ -16,6 +16,7 @@ export const GET_TWITTER_POSTS_ENDPOINT: string = API_URL + 'posts-twitter/';
export const SEARCH_ENDPOINT: string = API_URL + 'search/';
export const MOMENTS_ENDPOINT: string = API_URL + 'moments/';
export const VERIFY_INVITATION_CODE_ENDPOUNT: string = API_URL + 'verify-code/';
+export const COMMENTS_ENDPOINT: string = API_URL + 'comments/';
// Social Link
export const LINK_IG_ENDPOINT: string = API_URL + 'link-ig/';
diff --git a/src/routes/profile/Profile.tsx b/src/routes/profile/Profile.tsx
index 736127bf..8ab8ecde 100644
--- a/src/routes/profile/Profile.tsx
+++ b/src/routes/profile/Profile.tsx
@@ -5,11 +5,25 @@ import {
SocialMediaTaggs,
SearchScreen,
ProfileScreen,
+ MomentCommentsScreen,
} from '../../screens';
import {ProfileStack, ProfileStackParams} from './ProfileStack';
import {RouteProp} from '@react-navigation/native';
import {AvatarTitle} from '../../components';
+/**
+ * What will be the First Screen of the stack depends on value of isProfileView (Search if its true else Profile)
+ * Trying to explain the purpose of each route on the stack (ACTUALLY A STACK)
+ * Profile : To display the logged in user's profile when isProfileView is false, else displays profile of any user the logged in user wants to view.
+ * ProfileView : To display profile of a commenter / any user who has commented on a photo.
+ * When you click on the profile icon after looking at a user's profile, the stack is reset and you come back to the top of the stack (First screen : Profile in this case)
+ * Search : To display the search screen. Search for a user on this screen, click on a result tile and navigate to the same (isProfileView = true).
+ * When you click on the search icon after looking at a user's profile, the stack gets reset and you come back to the top of the stack (First screen : Search in this case)
+ * SocialMediaTaggs : To display user data for any social media account set up by the user.
+ * IndividualMoment : To display individual images uploaded by the user (Navigate to comments from this screen, click on a commenter's profile pic / username, look at a user's profile. Click on the profile icon again to come back to your own profile).
+ * MomentCommentsScreen : Displays comments posted by users on an image uploaded by the user.
+ */
+
type ProfileStackRouteProps = RouteProp;
interface ProfileStackProps {
@@ -76,6 +90,18 @@ const Profile: React.FC = ({route}) => {
options={{headerShown: false}}
initialParams={{isProfileView: isProfileView}}
/>
+
+
);
};
diff --git a/src/routes/profile/ProfileStack.tsx b/src/routes/profile/ProfileStack.tsx
index 1d7b907e..6d875e81 100644
--- a/src/routes/profile/ProfileStack.tsx
+++ b/src/routes/profile/ProfileStack.tsx
@@ -19,6 +19,13 @@ export type ProfileStackParams = {
moment: MomentType;
isProfileView: boolean;
};
+ MomentCommentsScreen: {
+ isProfileView: boolean;
+ moment_id: string;
+ };
+ ProfileView: {
+ isProfileView: boolean;
+ };
};
export const ProfileStack = createStackNavigator();
diff --git a/src/screens/profile/IndividualMoment.tsx b/src/screens/profile/IndividualMoment.tsx
index 639c0965..91f76f9b 100644
--- a/src/screens/profile/IndividualMoment.tsx
+++ b/src/screens/profile/IndividualMoment.tsx
@@ -1,15 +1,22 @@
import React, {useEffect, useState} from 'react';
import {StyleSheet, View, Image} from 'react-native';
import {Button} from 'react-native-elements';
-import {SCREEN_HEIGHT, SCREEN_WIDTH, StatusBarHeight} from '../../utils';
-import {UserType} from '../../types';
+import {
+ SCREEN_HEIGHT,
+ SCREEN_WIDTH,
+ StatusBarHeight,
+ getTimePosted,
+} from '../../utils';
+import {UserType, CommentType} from '../../types';
import {RouteProp} from '@react-navigation/native';
import {StackNavigationProp} from '@react-navigation/stack';
-import {CaptionScreenHeader} from '../../components/profile';
+import {CaptionScreenHeader} from '../../components';
import {AuthContext} from '../../routes/authentication';
import {ProfileStackParams} from 'src/routes/profile/ProfileStack';
-import moment from 'moment';
import Animated from 'react-native-reanimated';
+import {CommentsCount} from '../../components';
+import AsyncStorage from '@react-native-community/async-storage';
+import {getMomentCommentsCount} from '../../services';
const NO_USER: UserType = {
userId: '',
@@ -45,10 +52,13 @@ const IndividualMoment: React.FC = ({
const {isProfileView} = route.params;
const {
user: {userId},
+ logout,
} = React.useContext(AuthContext);
const [user, setUser] = useState(NO_USER);
const [caption, setCaption] = React.useState(route.params.moment.caption);
const [elapsedTime, setElapsedTime] = React.useState();
+ const [comments_count, setCommentsCount] = React.useState('');
+
const handleCaptionUpdate = (caption: string) => {
setCaption(caption);
};
@@ -58,35 +68,20 @@ const IndividualMoment: React.FC = ({
setUser(NO_USER);
}
const timePeriod = async () => {
- const datePosted = moment(date_time);
- const now = moment();
- var time = date_time;
- var difference = now.diff(datePosted, 'seconds');
+ setElapsedTime(getTimePosted(date_time));
+ };
- //Creating elapsedTime string to display to user
- // 0 to less than 1 minute
- if (difference < 60) {
- time = difference + 'seconds';
- }
- // 1 minute to less than 1 hour
- else if (difference >= 60 && difference < 60 * 60) {
- difference = now.diff(datePosted, 'minutes');
- time = difference + (difference === 1 ? 'minute' : 'minutes');
+ const loadComments = async () => {
+ const token = await AsyncStorage.getItem('token');
+ if (!token) {
+ logout();
+ return;
}
- //1 hour to less than 1 day
- else if (difference >= 60 * 60 && difference < 24 * 60 * 60) {
- difference = now.diff(datePosted, 'hours');
- time = difference + (difference === 1 ? 'hour' : 'hours');
- }
- //1 day to less than 7 days
- else if (difference >= 24 * 60 * 60 && difference < 7 * 24 * 60 * 60) {
- difference = now.diff(datePosted, 'days');
- time = difference + (difference === 1 ? 'day' : 'days');
- }
-
- setElapsedTime(time);
+ getMomentCommentsCount(moment_id, setCommentsCount, token);
};
+
timePeriod();
+ loadComments();
}, [date_time, userId]);
return (
@@ -109,10 +104,16 @@ const IndividualMoment: React.FC = ({
source={{uri: path_hash}}
resizeMode={'cover'}
/>
+
- {caption}
+
{elapsedTime}
+ {caption}
);
};
@@ -155,6 +156,8 @@ const styles = StyleSheet.create({
position: 'relative',
paddingBottom: '1%',
paddingTop: '1%',
+ marginLeft: '5%',
+ marginRight: '5%',
color: '#ffffff',
fontWeight: 'bold',
},
diff --git a/src/screens/profile/MomentCommentsScreen.tsx b/src/screens/profile/MomentCommentsScreen.tsx
new file mode 100644
index 00000000..30dde8b4
--- /dev/null
+++ b/src/screens/profile/MomentCommentsScreen.tsx
@@ -0,0 +1,133 @@
+import * as React from 'react';
+import {RouteProp, useNavigation} from '@react-navigation/native';
+import {ProfileStackParams} from '../../routes/profile';
+import {CenteredView, CommentTile, OverlayView} from '../../components';
+import {CommentType} from '../../types';
+import {ScrollView, StyleSheet, Text, View} from 'react-native';
+import {SCREEN_WIDTH} from '../../utils/screenDimensions';
+import {Button} from 'react-native-elements';
+import {AddComment} from '../../components/';
+import {useEffect} from 'react';
+import AsyncStorage from '@react-native-community/async-storage';
+import {AuthContext} from '../../routes/authentication';
+import {getMomentComments} from '../..//services';
+
+/**
+ * Comments Screen for an image uploaded
+ * Displays all comments for a particular moment uploaded by the user followed by a text area to add the comment.
+ * Comment is posted when return is pressed on the keypad.
+ */
+
+type MomentCommentsScreenRouteProps = RouteProp<
+ ProfileStackParams,
+ 'MomentCommentsScreen'
+>;
+
+interface MomentCommentsScreenProps {
+ route: MomentCommentsScreenRouteProps;
+}
+
+const MomentCommentsScreen: React.FC = ({route}) => {
+ const navigation = useNavigation();
+ const {isProfileView, moment_id} = route.params;
+ const [commentsList, setCommentsList] = React.useState([]);
+ const [newCommentsAvailable, setNewCommentsAvailable] = React.useState(true);
+ const {logout} = React.useContext(AuthContext);
+
+ useEffect(() => {
+ const loadComments = async () => {
+ const token = await AsyncStorage.getItem('token');
+ if (!token) {
+ logout();
+ return;
+ }
+ getMomentComments(moment_id, setCommentsList, token);
+ setNewCommentsAvailable(false);
+ };
+ if (newCommentsAvailable) {
+ loadComments();
+ }
+ }, [newCommentsAvailable]);
+
+ return (
+
+
+
+
+
+ {commentsList &&
+ commentsList.map((comment: CommentType) => (
+
+ ))}
+
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ header: {flexDirection: 'row'},
+ headerText: {
+ position: 'relative',
+ left: '180%',
+ alignSelf: 'center',
+ fontSize: 18,
+ fontWeight: '500',
+ },
+ container: {
+ position: 'relative',
+ top: '5%',
+ left: '5%',
+ backgroundColor: 'white',
+ borderRadius: 5,
+ width: SCREEN_WIDTH / 1.1,
+ height: '55%',
+ },
+ button: {
+ backgroundColor: 'transparent',
+ },
+ buttonText: {
+ color: 'black',
+ fontSize: 18,
+ fontWeight: '400',
+ },
+ modalView: {
+ width: '85%',
+ height: '70%',
+ backgroundColor: '#fff',
+ shadowColor: '#000',
+ shadowOpacity: 30,
+ shadowOffset: {width: 0, height: 2},
+ shadowRadius: 5,
+ borderRadius: 8,
+ paddingBottom: 15,
+ paddingHorizontal: 20,
+ paddingTop: 5,
+ justifyContent: 'space-between',
+ },
+ modalScrollViewContent: {
+ justifyContent: 'center',
+ },
+ modalScrollView: {
+ marginBottom: 10,
+ },
+});
+
+export default MomentCommentsScreen;
diff --git a/src/screens/profile/SocialMediaTaggs.tsx b/src/screens/profile/SocialMediaTaggs.tsx
index 5a2e638e..0ac6d1ef 100644
--- a/src/screens/profile/SocialMediaTaggs.tsx
+++ b/src/screens/profile/SocialMediaTaggs.tsx
@@ -1,6 +1,6 @@
import {RouteProp} from '@react-navigation/native';
import React from 'react';
-import {ScrollView, StatusBar, StyleSheet, View} from 'react-native';
+import {Alert, ScrollView, StatusBar, StyleSheet, View} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import {AVATAR_GRADIENT} from '../../constants';
import {
@@ -43,6 +43,7 @@ const SocialMediaTaggs: React.FC = ({route}) => {
socialAccounts,
} = context;
+
const handle = socialAccounts[socialMediaType].handle;
const posts = socialAccounts[socialMediaType].posts || [];
const headerHeight = headerBarHeightWithImage();
diff --git a/src/screens/profile/index.ts b/src/screens/profile/index.ts
index 6319c17d..9dfbe409 100644
--- a/src/screens/profile/index.ts
+++ b/src/screens/profile/index.ts
@@ -2,3 +2,4 @@ export {default as ProfileScreen} from './ProfileScreen';
export {default as SocialMediaTaggs} from './SocialMediaTaggs';
export {default as CaptionScreen} from './CaptionScreen';
export {default as IndividualMoment} from './IndividualMoment';
+export {default as MomentCommentsScreen} from './MomentCommentsScreen';
diff --git a/src/services/MomentServices.ts b/src/services/MomentServices.ts
new file mode 100644
index 00000000..60f516ce
--- /dev/null
+++ b/src/services/MomentServices.ts
@@ -0,0 +1,98 @@
+//Common moments api abstracted out here
+
+import {COMMENTS_ENDPOINT} from '../constants';
+import {Alert} from 'react-native';
+
+//Get all comments for a moment
+export const getMomentComments = async (
+ momentId: string,
+ callback: Function,
+ token: string,
+) => {
+ try {
+ 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);
+ }
+};
+
+//Post a comment on a moment
+export const postMomentComment = async (
+ commenter: string,
+ comment: string,
+ momentId: string,
+ token: string,
+) => {
+ try {
+ 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,
+ });
+ const status = response.status;
+ if (status === 200) {
+ const response_data = await response.json();
+ return response_data;
+ } else {
+ Alert.alert('Something went wrong! ðŸ˜', 'Not able to post a comment');
+ return {};
+ }
+ } catch (error) {
+ Alert.alert(
+ 'Something went wrong! ðŸ˜',
+ 'Not able to post a comment',
+ error,
+ );
+ return {};
+ }
+};
+
+//Get count of comments for a moment
+export const getMomentCommentsCount = async (
+ momentId: string,
+ callback: Function,
+ token: string,
+) => {
+ try {
+ 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,
+ );
+ }
+};
diff --git a/src/services/index.ts b/src/services/index.ts
index 5cd06cfe..2abcef95 100644
--- a/src/services/index.ts
+++ b/src/services/index.ts
@@ -1 +1,2 @@
export * from './UserProfileService';
+export * from './MomentServices';
diff --git a/src/types/types.ts b/src/types/types.ts
index aa46fd36..f9929017 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -74,3 +74,11 @@ export interface MomentType {
path_hash: string;
moment_id: string;
}
+
+export interface CommentType {
+ comment_id: string,
+ comment: string,
+ date_time: string,
+ commenter__id: string,
+ commenter__username: string
+}
diff --git a/src/utils/index.ts b/src/utils/index.ts
index 5bc168e3..a7e45979 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -1,2 +1,3 @@
export * from './screenDimensions';
export * from './statusBarHeight';
+export * from './moments';
diff --git a/src/utils/moments.ts b/src/utils/moments.ts
new file mode 100644
index 00000000..0f8021cb
--- /dev/null
+++ b/src/utils/moments.ts
@@ -0,0 +1,33 @@
+import moment from 'moment';
+
+//A util that calculates the difference between a given time and current time
+//Returns the difference in the largest possible unit of time (days > hours > minutes > seconds)
+
+export const getTimePosted = (date_time: string) => {
+ const datePosted = moment(date_time);
+ const now = moment();
+ var time = date_time;
+ var difference = now.diff(datePosted, 'seconds');
+
+ //Creating elapsedTime string to display to user
+ // 0 to less than 1 minute
+ if (difference < 60) {
+ time = difference + ' seconds';
+ }
+ // 1 minute to less than 1 hour
+ else if (difference >= 60 && difference < 60 * 60) {
+ difference = now.diff(datePosted, 'minutes');
+ time = difference + (difference === 1 ? ' minute' : ' minutes');
+ }
+ //1 hour to less than 1 day
+ else if (difference >= 60 * 60 && difference < 24 * 60 * 60) {
+ difference = now.diff(datePosted, 'hours');
+ time = difference + (difference === 1 ? ' hour' : ' hours');
+ }
+ //1 day to less than 7 days
+ else if (difference >= 24 * 60 * 60 && difference < 7 * 24 * 60 * 60) {
+ difference = now.diff(datePosted, 'days');
+ time = difference + (difference === 1 ? ' day' : ' days');
+ }
+ return time;
+};
--
cgit v1.2.3-70-g09d2