aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIvan Chen <ivan@tagg.id>2021-06-11 18:12:24 -0400
committerGitHub <noreply@github.com>2021-06-11 18:12:24 -0400
commit78f32c1400eff46d4c768b78fbaf672826c74285 (patch)
tree00e62c1821d4973d214fdd47f8293749972c1925 /src
parent4add0eed33032012fb945fb02a928aed426b9465 (diff)
parent24f79b9cb3456b3901155ed2e4c8fc66710b97b2 (diff)
Merge pull request #466 from IvanIFChen/tma904-moment-comment-revamp
[TMA-904] Moment Comment Revamp
Diffstat (limited to 'src')
-rw-r--r--src/App.tsx19
-rw-r--r--src/components/comments/AddComment.tsx96
-rw-r--r--src/components/comments/CommentsCount.tsx58
-rw-r--r--src/components/comments/index.ts1
-rw-r--r--src/components/common/BottomDrawer.tsx2
-rw-r--r--src/components/moments/IndividualMomentTitleBar.tsx12
-rw-r--r--src/components/moments/MomentCommentPreview.tsx97
-rw-r--r--src/components/moments/MomentPost.tsx65
-rw-r--r--src/components/moments/MomentPostContent.tsx147
-rw-r--r--src/components/moments/MomentPostHeader.tsx3
-rw-r--r--src/components/suggestedPeople/legacy/BadgesDropdown.tsx6
-rw-r--r--src/routes/main/MainStackScreen.tsx1
-rw-r--r--src/screens/onboarding/BasicInfoOnboarding.tsx6
-rw-r--r--src/screens/profile/IndividualMoment.tsx135
-rw-r--r--src/screens/profile/MomentCommentsScreen.tsx5
-rw-r--r--src/services/MomentService.ts21
-rw-r--r--src/store/initialStates.ts11
-rw-r--r--src/types/types.ts12
-rw-r--r--src/utils/comments.tsx35
-rw-r--r--src/utils/moments.ts8
-rw-r--r--src/utils/users.ts18
21 files changed, 458 insertions, 300 deletions
diff --git a/src/App.tsx b/src/App.tsx
index 92e7abee..64f40bae 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -27,16 +27,15 @@ export const ChatContext = React.createContext({} as ChatContextType);
const App = () => {
const routeNameRef = useRef();
const [channel, setChannel] = useState<ChannelGroupedType>();
- const chatClient =
- StreamChat.getInstance<
- LocalAttachmentType,
- LocalChannelType,
- LocalCommandType,
- LocalEventType,
- LocalMessageType,
- LocalResponseType,
- LocalUserType
- >(STREAM_CHAT_API);
+ const chatClient = StreamChat.getInstance<
+ LocalAttachmentType,
+ LocalChannelType,
+ LocalCommandType,
+ LocalEventType,
+ LocalMessageType,
+ LocalResponseType,
+ LocalUserType
+ >(STREAM_CHAT_API);
return (
<Provider store={store}>
<NavigationContainer
diff --git a/src/components/comments/AddComment.tsx b/src/components/comments/AddComment.tsx
index b229d010..9667046c 100644
--- a/src/components/comments/AddComment.tsx
+++ b/src/components/comments/AddComment.tsx
@@ -24,10 +24,21 @@ import {MentionInputControlled} from './MentionInputControlled';
export interface AddCommentProps {
momentId: string;
placeholderText: string;
+ callback?: (message: string) => void;
+ onFocus?: () => void;
+ isKeyboardAvoiding?: boolean;
+ theme?: 'dark' | 'white';
}
-const AddComment: React.FC<AddCommentProps> = ({momentId, placeholderText}) => {
- const {setShouldUpdateAllComments, commentTapped} =
+const AddComment: React.FC<AddCommentProps> = ({
+ momentId,
+ placeholderText,
+ callback = (_) => null,
+ onFocus = () => null,
+ isKeyboardAvoiding = true,
+ theme = 'white',
+}) => {
+ const {setShouldUpdateAllComments = () => null, commentTapped} =
useContext(CommentContext);
const [inReplyToMention, setInReplyToMention] = useState('');
const [comment, setComment] = useState('');
@@ -50,13 +61,15 @@ const AddComment: React.FC<AddCommentProps> = ({momentId, placeholderText}) => {
if (trimmed === '') {
return;
}
+ const message = inReplyToMention + trimmed;
const postedComment = await postComment(
- inReplyToMention + trimmed,
+ message,
objectId,
isReplyingToComment || isReplyingToReply,
);
if (postedComment) {
+ callback(message);
setComment('');
setInReplyToMention('');
@@ -100,43 +113,63 @@ const AddComment: React.FC<AddCommentProps> = ({momentId, placeholderText}) => {
}
}, [isReplyingToComment, isReplyingToReply, commentTapped]);
- return (
- <KeyboardAvoidingView
- behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
- keyboardVerticalOffset={SCREEN_HEIGHT * 0.1}>
- <View
- style={[
- styles.container,
- keyboardVisible ? styles.whiteBackround : {},
- ]}>
- <View style={styles.textContainer}>
- <Avatar style={styles.avatar} uri={avatar} />
- <MentionInputControlled
- containerStyle={styles.text}
- placeholder={placeholderText}
- value={inReplyToMention + comment}
- onChange={(newText: string) => {
- // skipping the `inReplyToMention` text
- setComment(
- newText.substring(inReplyToMention.length, newText.length),
- );
- }}
- inputRef={ref}
- partTypes={mentionPartTypes('blue')}
- />
+ const mainContent = () => (
+ <View
+ style={[
+ theme === 'white' ? styles.containerWhite : styles.containerDark,
+ keyboardVisible && theme !== 'dark' ? styles.whiteBackround : {},
+ ]}>
+ <View style={styles.textContainer}>
+ <Avatar style={styles.avatar} uri={avatar} />
+ <MentionInputControlled
+ containerStyle={styles.text}
+ placeholderTextColor={theme === 'dark' ? '#828282' : undefined}
+ placeholder={placeholderText}
+ value={inReplyToMention + comment}
+ onFocus={onFocus}
+ onChange={(newText: string) => {
+ // skipping the `inReplyToMention` text
+ setComment(
+ newText.substring(inReplyToMention.length, newText.length),
+ );
+ }}
+ inputRef={ref}
+ partTypes={mentionPartTypes('blue')}
+ />
+ {(theme === 'white' || (theme === 'dark' && keyboardVisible)) && (
<View style={styles.submitButton}>
- <TouchableOpacity style={styles.submitButton} onPress={addComment}>
+ <TouchableOpacity
+ style={
+ comment === ''
+ ? [styles.submitButton, styles.greyButton]
+ : styles.submitButton
+ }
+ disabled={comment === ''}
+ onPress={addComment}>
<UpArrowIcon width={35} height={35} color={'white'} />
</TouchableOpacity>
</View>
- </View>
+ )}
</View>
+ </View>
+ );
+ return isKeyboardAvoiding ? (
+ <KeyboardAvoidingView
+ behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
+ keyboardVerticalOffset={SCREEN_HEIGHT * 0.1}>
+ {mainContent()}
</KeyboardAvoidingView>
+ ) : (
+ mainContent()
);
};
const styles = StyleSheet.create({
- container: {
+ containerDark: {
+ alignItems: 'center',
+ width: SCREEN_WIDTH,
+ },
+ containerWhite: {
backgroundColor: '#f7f7f7',
alignItems: 'center',
width: SCREEN_WIDTH,
@@ -176,6 +209,9 @@ const styles = StyleSheet.create({
marginVertical: '2%',
alignSelf: 'flex-end',
},
+ greyButton: {
+ backgroundColor: 'grey',
+ },
whiteBackround: {
backgroundColor: '#fff',
},
diff --git a/src/components/comments/CommentsCount.tsx b/src/components/comments/CommentsCount.tsx
deleted file mode 100644
index f4f8197d..00000000
--- a/src/components/comments/CommentsCount.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import {useNavigation} from '@react-navigation/native';
-import * as React from 'react';
-import {StyleSheet, TouchableOpacity} from 'react-native';
-import {Text} from 'react-native-animatable';
-import CommentIcon from '../../assets/icons/moment-comment-icon.svg';
-import {ScreenType} from '../../types';
-
-/**
- * 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 = {
- commentsCount: string;
- momentId: string;
- screenType: ScreenType;
-};
-
-const CommentsCount: React.FC<CommentsCountProps> = ({
- commentsCount,
- momentId,
- screenType,
-}) => {
- const navigation = useNavigation();
- const navigateToCommentsScreen = async () => {
- navigation.push('MomentCommentsScreen', {
- moment_id: momentId,
- screenType,
- });
- };
- return (
- <>
- <TouchableOpacity onPress={navigateToCommentsScreen}>
- <CommentIcon style={styles.image} />
- <Text style={styles.count}>
- {commentsCount !== '0' ? commentsCount : ''}
- </Text>
- </TouchableOpacity>
- </>
- );
-};
-
-const styles = StyleSheet.create({
- image: {
- position: 'relative',
- width: 21,
- height: 21,
- },
- count: {
- position: 'relative',
- fontWeight: 'bold',
- color: 'white',
- paddingTop: '3%',
- textAlign: 'center',
- },
-});
-
-export default CommentsCount;
diff --git a/src/components/comments/index.ts b/src/components/comments/index.ts
index 6293f799..ebd93844 100644
--- a/src/components/comments/index.ts
+++ b/src/components/comments/index.ts
@@ -1,3 +1,2 @@
-export {default as CommentsCount} from '../comments/CommentsCount';
export {default as CommentTile} from './CommentTile';
export {default as AddComment} from './AddComment';
diff --git a/src/components/common/BottomDrawer.tsx b/src/components/common/BottomDrawer.tsx
index 16e98690..b79b8820 100644
--- a/src/components/common/BottomDrawer.tsx
+++ b/src/components/common/BottomDrawer.tsx
@@ -23,7 +23,7 @@ const BottomDrawer: React.FC<BottomDrawerProps> = (props) => {
const {isOpen, setIsOpen, showHeader, initialSnapPosition} = props;
const drawerRef = useRef<BottomSheet>(null);
const [modalVisible, setModalVisible] = useState(isOpen);
- const bgAlpha = useValue(isOpen ? 1 : 0);
+ const bgAlpha = useValue(isOpen ? 0 : 1);
useEffect(() => {
if (isOpen) {
diff --git a/src/components/moments/IndividualMomentTitleBar.tsx b/src/components/moments/IndividualMomentTitleBar.tsx
index 79453ade..4ae9471f 100644
--- a/src/components/moments/IndividualMomentTitleBar.tsx
+++ b/src/components/moments/IndividualMomentTitleBar.tsx
@@ -1,8 +1,13 @@
import React from 'react';
-import {TouchableOpacity} from 'react-native';
-import {Text, View, StyleSheet, ViewProps} from 'react-native';
-import {normalize} from '../../utils';
+import {
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ View,
+ ViewProps,
+} from 'react-native';
import CloseIcon from '../../assets/ionicons/close-outline.svg';
+import {normalize} from '../../utils';
interface IndividualMomentTitleBarProps extends ViewProps {
title: string;
@@ -30,7 +35,6 @@ const styles = StyleSheet.create({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start',
- height: '5%',
},
headerContainer: {
width: '80%',
diff --git a/src/components/moments/MomentCommentPreview.tsx b/src/components/moments/MomentCommentPreview.tsx
new file mode 100644
index 00000000..e53ed258
--- /dev/null
+++ b/src/components/moments/MomentCommentPreview.tsx
@@ -0,0 +1,97 @@
+import {useNavigation} from '@react-navigation/native';
+import React from 'react';
+import {Image, StyleSheet, Text, View} from 'react-native';
+import {TouchableOpacity} from 'react-native-gesture-handler';
+import {useDispatch, useStore} from 'react-redux';
+import {MomentCommentPreviewType, ScreenType, UserType} from '../../types';
+import {navigateToProfile, normalize} from '../../utils';
+import {mentionPartTypes, renderTextWithMentions} from '../../utils/comments';
+
+interface MomentCommentPreviewProps {
+ momentId: string;
+ commentsCount: number;
+ commentPreview: MomentCommentPreviewType | null;
+ screenType: ScreenType;
+}
+
+const MomentCommentPreview: React.FC<MomentCommentPreviewProps> = ({
+ momentId,
+ commentsCount,
+ commentPreview,
+ screenType,
+}) => {
+ const navigation = useNavigation();
+ const state = useStore().getState();
+ const commentCountText =
+ commentsCount === 0 ? 'No Comments' : commentsCount + ' comments';
+
+ return (
+ <TouchableOpacity
+ style={styles.commentsPreviewContainer}
+ onPress={() =>
+ navigation.push('MomentCommentsScreen', {
+ moment_id: momentId,
+ screenType,
+ })
+ }>
+ <Text style={styles.whiteBold}>{commentCountText}</Text>
+ {commentPreview !== null && (
+ <View style={styles.previewContainer}>
+ <Image
+ source={{
+ uri: commentPreview.commenter.thumbnail_url,
+ }}
+ style={styles.avatar}
+ />
+ <Text style={styles.whiteBold} numberOfLines={1}>
+ <Text> </Text>
+ <Text>{commentPreview.commenter.username}</Text>
+ <Text> </Text>
+ {renderTextWithMentions({
+ value: commentPreview.comment,
+ styles: styles.normalFont,
+ partTypes: mentionPartTypes('white'),
+ onPress: (user: UserType) =>
+ navigateToProfile(
+ state,
+ useDispatch,
+ navigation,
+ screenType,
+ user,
+ ),
+ })}
+ </Text>
+ </View>
+ )}
+ </TouchableOpacity>
+ );
+};
+
+const styles = StyleSheet.create({
+ commentsPreviewContainer: {
+ height: normalize(50),
+ flexDirection: 'column',
+ justifyContent: 'space-around',
+ marginHorizontal: '5%',
+ marginBottom: '2%',
+ },
+ whiteBold: {
+ fontWeight: '700',
+ color: 'white',
+ fontSize: normalize(13),
+ },
+ previewContainer: {
+ flexDirection: 'row',
+ width: '95%',
+ },
+ avatar: {
+ height: normalize(16),
+ width: normalize(16),
+ borderRadius: 99,
+ },
+ normalFont: {
+ fontWeight: 'normal',
+ },
+});
+
+export default MomentCommentPreview;
diff --git a/src/components/moments/MomentPost.tsx b/src/components/moments/MomentPost.tsx
index b659177d..d87028e3 100644
--- a/src/components/moments/MomentPost.tsx
+++ b/src/components/moments/MomentPost.tsx
@@ -1,21 +1,25 @@
import React, {useEffect, useState} from 'react';
-import {StyleSheet, View} from 'react-native';
+import {StyleSheet} from 'react-native';
import {useSelector} from 'react-redux';
import {MomentPostContent, MomentPostHeader} from '.';
import {deleteMomentTag, loadMomentTags} from '../../services';
import {RootState} from '../../store/rootReducer';
-import {MomentTagType, MomentType, ScreenType} from '../../types';
-import {SCREEN_HEIGHT, SCREEN_WIDTH, StatusBarHeight} from '../../utils';
+import {MomentPostType, MomentTagType, ScreenType} from '../../types';
+import {normalize, SCREEN_HEIGHT} from '../../utils';
interface MomentPostProps {
- item: MomentType;
+ moment: MomentPostType;
userXId: string | undefined;
screenType: ScreenType;
+ index: number;
}
-const ITEM_HEIGHT = SCREEN_HEIGHT * 0.9;
-
-const MomentPost: React.FC<MomentPostProps> = ({item, userXId, screenType}) => {
+const MomentPost: React.FC<MomentPostProps> = ({
+ moment,
+ userXId,
+ screenType,
+ index,
+}) => {
const {userId: loggedInUserId, username: loggedInUsername} = useSelector(
(state: RootState) => state.user.user,
);
@@ -30,17 +34,14 @@ const MomentPost: React.FC<MomentPostProps> = ({item, userXId, screenType}) => {
const isOwnProfile = username === loggedInUsername;
- const loadTags = async () => {
- const response = await loadMomentTags(item.moment_id);
- setTags(response ? response : []);
- };
-
/*
* Load tags on initial render to pass tags data to moment header and content
*/
useEffect(() => {
- loadTags();
- }, [item]);
+ loadMomentTags(moment.moment_id).then((response) => {
+ setTags(response ? response : []);
+ });
+ }, []);
/*
* Check if loggedInUser has been tagged in the picture and set the id
@@ -71,50 +72,34 @@ const MomentPost: React.FC<MomentPostProps> = ({item, userXId, screenType}) => {
};
return (
- <View style={styles.postContainer}>
+ <>
<MomentPostHeader
+ style={styles.postHeader}
userXId={userXId}
screenType={screenType}
username={isOwnProfile ? loggedInUsername : username}
- style={styles.postHeader}
momentTagId={momentTagId}
removeTag={removeTag}
- moment={item}
+ moment={moment}
tags={tags}
/>
<MomentPostContent
style={styles.postContent}
+ moment={moment}
screenType={screenType}
- moment={item}
momentTags={tags}
+ index={index}
/>
- </View>
+ </>
);
};
const styles = StyleSheet.create({
- contentContainer: {
- width: SCREEN_WIDTH,
- height: SCREEN_HEIGHT,
- paddingTop: StatusBarHeight,
- flex: 1,
- paddingBottom: 0,
- },
- content: {
- flex: 9,
- },
- header: {
- flex: 1,
- },
- postContainer: {
- height: ITEM_HEIGHT,
- width: SCREEN_WIDTH,
- flex: 1,
- },
- postHeader: {
- flex: 1,
+ postHeader: {},
+ postContent: {
+ minHeight: SCREEN_HEIGHT * 0.8,
+ paddingBottom: normalize(20),
},
- postContent: {flex: 9},
});
export default MomentPost;
diff --git a/src/components/moments/MomentPostContent.tsx b/src/components/moments/MomentPostContent.tsx
index d831d7ee..aca2999c 100644
--- a/src/components/moments/MomentPostContent.tsx
+++ b/src/components/moments/MomentPostContent.tsx
@@ -1,71 +1,81 @@
import {useNavigation} from '@react-navigation/native';
-import React, {useEffect, useRef, useState} from 'react';
+import React, {useContext, useEffect, useRef, useState} from 'react';
import {Image, StyleSheet, Text, View, ViewProps} from 'react-native';
import {TouchableWithoutFeedback} from 'react-native-gesture-handler';
-import Animated, {Easing} from 'react-native-reanimated';
+import Animated, {EasingNode} from 'react-native-reanimated';
import {useDispatch, useStore} from 'react-redux';
-import {getCommentsCount} from '../../services';
+import {MomentContext} from '../../screens/profile/IndividualMoment';
import {RootState} from '../../store/rootReducer';
-import {MomentTagType, MomentType, ScreenType, UserType} from '../../types';
import {
+ MomentCommentPreviewType,
+ MomentPostType,
+ MomentTagType,
+ ScreenType,
+ UserType,
+} from '../../types';
+import {
+ getLoggedInUserAsProfilePreview,
getTimePosted,
navigateToProfile,
normalize,
- SCREEN_HEIGHT,
SCREEN_WIDTH,
} from '../../utils';
import {mentionPartTypes, renderTextWithMentions} from '../../utils/comments';
-import {CommentsCount} from '../comments';
+import {AddComment} from '../comments';
import {MomentTags} from '../common';
+import MomentCommentPreview from './MomentCommentPreview';
interface MomentPostContentProps extends ViewProps {
screenType: ScreenType;
- moment: MomentType;
+ moment: MomentPostType;
momentTags: MomentTagType[];
+ index: number;
}
const MomentPostContent: React.FC<MomentPostContentProps> = ({
screenType,
- style,
moment,
+ style,
momentTags,
+ index,
}) => {
- const [elapsedTime, setElapsedTime] = useState('');
- const [comments_count, setCommentsCount] = useState('');
const [tags, setTags] = useState<MomentTagType[]>(momentTags);
const state: RootState = useStore().getState();
const navigation = useNavigation();
const dispatch = useDispatch();
const imageRef = useRef(null);
const [visible, setVisible] = useState(false);
-
const [fadeValue, setFadeValue] = useState<Animated.Value<number>>(
new Animated.Value(0),
);
+ const [commentCount, setCommentCount] = useState<number>(
+ moment.comments_count,
+ );
+ const [commentPreview, setCommentPreview] =
+ useState<MomentCommentPreviewType | null>(moment.comment_preview);
+ const {keyboardVisible, scrollTo} = useContext(MomentContext);
+ const [hideText, setHideText] = useState(false);
useEffect(() => {
setTags(momentTags);
}, [momentTags]);
useEffect(() => {
- const fetchCommentsCount = async () => {
- const count = await getCommentsCount(moment.moment_id, false);
- setCommentsCount(count);
- };
- setElapsedTime(getTimePosted(moment.date_created));
- fetchCommentsCount();
- }, [moment.date_created, moment.moment_id]);
-
- useEffect(() => {
const fade = async () => {
Animated.timing(fadeValue, {
toValue: 1,
duration: 250,
- easing: Easing.linear,
+ easing: EasingNode.linear,
}).start();
};
fade();
}, [fadeValue]);
+ useEffect(() => {
+ if (!keyboardVisible && hideText) {
+ setHideText(false);
+ }
+ }, [keyboardVisible, hideText]);
+
return (
<View style={[styles.container, style]}>
<TouchableWithoutFeedback
@@ -82,70 +92,89 @@ const MomentPostContent: React.FC<MomentPostContentProps> = ({
{tags.length > 0 && (
<Image
source={require('../../assets/icons/tag_indicate.png')}
- style={[styles.tagIcon]}
+ style={styles.tagIcon}
/>
)}
</TouchableWithoutFeedback>
{visible && (
<Animated.View style={[styles.tapTag, {opacity: fadeValue}]}>
- <MomentTags editing={false} tags={tags} imageRef={imageRef} />
+ <MomentTags
+ editing={false}
+ tags={tags}
+ setTags={() => null}
+ imageRef={imageRef}
+ />
</Animated.View>
)}
- <View style={styles.footerContainer}>
- <CommentsCount
- commentsCount={comments_count}
- momentId={moment.moment_id}
- screenType={screenType}
- />
- <Text style={styles.text}>{elapsedTime}</Text>
- </View>
- {renderTextWithMentions({
- value: moment.caption,
- styles: styles.captionText,
- partTypes: mentionPartTypes('white'),
- onPress: (user: UserType) =>
- navigateToProfile(state, dispatch, navigation, screenType, user),
- })}
+ {!hideText && (
+ <>
+ {moment.caption !== '' &&
+ renderTextWithMentions({
+ value: moment.caption,
+ styles: styles.captionText,
+ partTypes: mentionPartTypes('white'),
+ onPress: (user: UserType) =>
+ navigateToProfile(
+ state,
+ dispatch,
+ navigation,
+ screenType,
+ user,
+ ),
+ })}
+ <MomentCommentPreview
+ momentId={moment.moment_id}
+ commentsCount={commentCount}
+ commentPreview={commentPreview}
+ screenType={screenType}
+ />
+ </>
+ )}
+ <AddComment
+ placeholderText={'Add a comment here!'}
+ momentId={moment.moment_id}
+ callback={(message) => {
+ setCommentPreview({
+ commenter: getLoggedInUserAsProfilePreview(state),
+ comment: message,
+ });
+ setCommentCount(commentCount + 1);
+ }}
+ onFocus={() => {
+ setHideText(true);
+ scrollTo(index);
+ }}
+ isKeyboardAvoiding={false}
+ theme={'dark'}
+ />
+ <Text style={styles.text}>{getTimePosted(moment.date_created)}</Text>
</View>
);
};
const styles = StyleSheet.create({
- container: {
- height: SCREEN_HEIGHT,
- },
+ container: {},
image: {
width: SCREEN_WIDTH,
aspectRatio: 1,
marginBottom: '3%',
},
- footerContainer: {
- flexDirection: 'row',
- justifyContent: 'space-between',
- marginLeft: '7%',
- marginRight: '5%',
- marginBottom: '2%',
- },
text: {
- position: 'relative',
- paddingBottom: '1%',
- paddingTop: '1%',
- marginLeft: '7%',
- marginRight: '2%',
- color: '#ffffff',
- fontWeight: 'bold',
+ marginHorizontal: '5%',
+ color: 'white',
+ fontWeight: '500',
+ textAlign: 'right',
+ marginTop: 5,
},
captionText: {
position: 'relative',
- paddingBottom: '34%',
- paddingTop: '1%',
- marginLeft: '5%',
- marginRight: '5%',
+ marginHorizontal: '5%',
color: '#ffffff',
fontWeight: '500',
fontSize: normalize(13),
lineHeight: normalize(15.51),
letterSpacing: normalize(0.6),
+ marginBottom: normalize(18),
},
tapTag: {
position: 'absolute',
diff --git a/src/components/moments/MomentPostHeader.tsx b/src/components/moments/MomentPostHeader.tsx
index cde7639c..5f26951a 100644
--- a/src/components/moments/MomentPostHeader.tsx
+++ b/src/components/moments/MomentPostHeader.tsx
@@ -64,6 +64,7 @@ const MomentPostHeader: React.FC<MomentPostHeaderProps> = ({
style={styles.avatar}
userXId={userXId}
screenType={screenType}
+ editable={false}
/>
<Text style={styles.headerText}>{username}</Text>
</TouchableOpacity>
@@ -75,7 +76,7 @@ const MomentPostHeader: React.FC<MomentPostHeaderProps> = ({
removeTag={removeTag}
dismissScreenAndUpdate={() => {
dispatch(loadUserMoments(loggedInUserId));
- navigation.pop();
+ navigation.goBack();
}}
screenType={screenType}
moment={moment}
diff --git a/src/components/suggestedPeople/legacy/BadgesDropdown.tsx b/src/components/suggestedPeople/legacy/BadgesDropdown.tsx
index 2c177e69..307205b8 100644
--- a/src/components/suggestedPeople/legacy/BadgesDropdown.tsx
+++ b/src/components/suggestedPeople/legacy/BadgesDropdown.tsx
@@ -1,7 +1,7 @@
import React, {useEffect, useState} from 'react';
import {StyleSheet} from 'react-native';
import {TouchableOpacity} from 'react-native-gesture-handler';
-import Animated, {Easing} from 'react-native-reanimated';
+import Animated, {EasingNode} from 'react-native-reanimated';
import {BadgeIcon, UniversityIcon} from '../..';
import {UniversityBadgeDisplayType, UniversityType} from '../../../types';
import {normalize} from '../../../utils';
@@ -41,7 +41,7 @@ const BadgesDropdown: React.FC<BadgesDropdownProps> = ({
Animated.timing(top[i], {
toValue: i * 40 + 50,
duration: 150,
- easing: Easing.linear,
+ easing: EasingNode.linear,
}).start();
}
}
@@ -54,7 +54,7 @@ const BadgesDropdown: React.FC<BadgesDropdownProps> = ({
Animated.timing(top[i], {
toValue: 0,
duration: 150,
- easing: Easing.linear,
+ easing: EasingNode.linear,
}).start();
}
}
diff --git a/src/routes/main/MainStackScreen.tsx b/src/routes/main/MainStackScreen.tsx
index f6a012d6..3be2ff28 100644
--- a/src/routes/main/MainStackScreen.tsx
+++ b/src/routes/main/MainStackScreen.tsx
@@ -245,7 +245,6 @@ const MainStackScreen: React.FC<MainStackProps> = ({route}) => {
<MainStack.Screen
name="InviteFriendsScreen"
component={InviteFriendsScreen}
- initialParams={{screenType}}
options={{
...headerBarOptions('black', 'Invites'),
}}
diff --git a/src/screens/onboarding/BasicInfoOnboarding.tsx b/src/screens/onboarding/BasicInfoOnboarding.tsx
index e5e6f59b..d5998ac1 100644
--- a/src/screens/onboarding/BasicInfoOnboarding.tsx
+++ b/src/screens/onboarding/BasicInfoOnboarding.tsx
@@ -13,7 +13,7 @@ import {
TouchableOpacity,
} from 'react-native';
import {normalize} from 'react-native-elements';
-import Animated, {Easing, useValue} from 'react-native-reanimated';
+import Animated, {EasingNode, useValue} from 'react-native-reanimated';
import {
ArrowButton,
Background,
@@ -99,7 +99,7 @@ const BasicInfoOnboarding: React.FC<BasicInfoOnboardingProps> = ({route}) => {
Animated.timing(fadeButtonValue, {
toValue: target,
duration: 100,
- easing: Easing.linear,
+ easing: EasingNode.linear,
}).start();
};
@@ -108,7 +108,7 @@ const BasicInfoOnboarding: React.FC<BasicInfoOnboardingProps> = ({route}) => {
Animated.timing(fadeValue, {
toValue: 1,
duration: 1000,
- easing: Easing.linear,
+ easing: EasingNode.linear,
}).start();
};
fade();
diff --git a/src/screens/profile/IndividualMoment.tsx b/src/screens/profile/IndividualMoment.tsx
index 4ad4515d..f8113aba 100644
--- a/src/screens/profile/IndividualMoment.tsx
+++ b/src/screens/profile/IndividualMoment.tsx
@@ -1,103 +1,138 @@
import {BlurView} from '@react-native-community/blur';
import {RouteProp} from '@react-navigation/native';
import {StackNavigationProp} from '@react-navigation/stack';
-import React from 'react';
-import {FlatList, StyleSheet, View} from 'react-native';
+import React, {useEffect, useRef, useState} from 'react';
+import {FlatList, Keyboard, StyleSheet} from 'react-native';
import {useSelector} from 'react-redux';
import {IndividualMomentTitleBar, MomentPost} from '../../components';
+import {AVATAR_DIM} from '../../constants';
import {MainStackParams} from '../../routes';
import {RootState} from '../../store/rootreducer';
-import {MomentType} from '../../types';
-import {SCREEN_HEIGHT, SCREEN_WIDTH, StatusBarHeight} from '../../utils';
+import {MomentPostType} from '../../types';
+import {
+ isIPhoneX,
+ normalize,
+ SCREEN_HEIGHT,
+ StatusBarHeight,
+} from '../../utils';
/**
* Individual moment view opened when user clicks on a moment tile
*/
+
+type MomentContextType = {
+ keyboardVisible: boolean;
+ scrollTo: (index: number) => void;
+};
+
+export const MomentContext = React.createContext({} as MomentContextType);
+
type IndividualMomentRouteProp = RouteProp<MainStackParams, 'IndividualMoment'>;
+
type IndividualMomentNavigationProp = StackNavigationProp<
MainStackParams,
'IndividualMoment'
>;
+
interface IndividualMomentProps {
route: IndividualMomentRouteProp;
navigation: IndividualMomentNavigationProp;
}
-const ITEM_HEIGHT = SCREEN_HEIGHT * 0.9;
-
const IndividualMoment: React.FC<IndividualMomentProps> = ({
route,
navigation,
}) => {
- const {moment_category, moment_id} = route.params.moment;
- const {userXId, screenType} = route.params;
-
+ const {
+ userXId,
+ screenType,
+ moment: {moment_category, moment_id},
+ } = route.params;
const {moments} = useSelector((state: RootState) =>
userXId ? state.userX[screenType][userXId] : state.moments,
);
-
+ const scrollRef = useRef<FlatList<MomentPostType>>(null);
const momentData = moments.filter(
(m) => m.moment_category === moment_category,
);
const initialIndex = momentData.findIndex((m) => m.moment_id === moment_id);
+ const [keyboardVisible, setKeyboardVisible] = useState(false);
+
+ useEffect(() => {
+ const showKeyboard = () => setKeyboardVisible(true);
+ const hideKeyboard = () => setKeyboardVisible(false);
+ Keyboard.addListener('keyboardWillShow', showKeyboard);
+ Keyboard.addListener('keyboardWillHide', hideKeyboard);
+ return () => {
+ Keyboard.removeListener('keyboardWillShow', showKeyboard);
+ Keyboard.removeListener('keyboardWillHide', hideKeyboard);
+ };
+ }, []);
+
+ const scrollTo = (index: number) => {
+ // TODO: make this dynamic
+ const offset = isIPhoneX() ? -(AVATAR_DIM + 100) : -(AVATAR_DIM + 160);
+ scrollRef.current?.scrollToIndex({
+ index: index,
+ viewOffset: offset,
+ });
+ };
return (
- <BlurView
- blurType="light"
- blurAmount={30}
- reducedTransparencyFallbackColor="white"
- style={styles.contentContainer}>
- <IndividualMomentTitleBar
- style={styles.header}
- close={() => navigation.pop()}
- {...{title: moment_category}}
- />
- <View style={styles.content}>
+ <MomentContext.Provider
+ value={{
+ keyboardVisible,
+ scrollTo,
+ }}>
+ <BlurView
+ blurType="light"
+ blurAmount={30}
+ reducedTransparencyFallbackColor="white"
+ style={styles.contentContainer}>
+ <IndividualMomentTitleBar
+ style={styles.header}
+ close={() => navigation.goBack()}
+ title={moment_category}
+ />
<FlatList
+ ref={scrollRef}
data={momentData}
- renderItem={({item}: {item: MomentType}) => (
- <MomentPost userXId={userXId} screenType={screenType} item={item} />
+ contentContainerStyle={styles.listContentContainer}
+ renderItem={({item, index}) => (
+ <MomentPost
+ moment={item}
+ userXId={userXId}
+ screenType={screenType}
+ index={index}
+ />
)}
- keyExtractor={(item, index) => index.toString()}
+ keyExtractor={(item, _) => item.moment_id}
showsVerticalScrollIndicator={false}
- snapToAlignment={'start'}
- snapToInterval={ITEM_HEIGHT}
- decelerationRate={'fast'}
initialScrollIndex={initialIndex}
- getItemLayout={(data, index) => ({
- length: ITEM_HEIGHT,
- offset: ITEM_HEIGHT * index,
- index,
- })}
- pagingEnabled
+ onScrollToIndexFailed={() => {
+ // TODO: code below does not work, index resets to 0
+ // const wait = new Promise((resolve) => setTimeout(resolve, 500));
+ // wait.then(() => {
+ // console.log('scrolling to ', initialIndex);
+ // scrollRef.current?.scrollToIndex({index: initialIndex});
+ // });
+ }}
/>
- </View>
- </BlurView>
+ </BlurView>
+ </MomentContext.Provider>
);
};
const styles = StyleSheet.create({
contentContainer: {
- width: SCREEN_WIDTH,
- height: SCREEN_HEIGHT,
paddingTop: StatusBarHeight,
flex: 1,
- paddingBottom: 0,
- },
- content: {
- flex: 9,
},
header: {
- flex: 1,
- },
- postContainer: {
- height: ITEM_HEIGHT,
- width: SCREEN_WIDTH,
- flex: 1,
+ height: normalize(70),
},
- postHeader: {
- flex: 1,
+ listContentContainer: {
+ paddingBottom: SCREEN_HEIGHT * 0.2,
},
- postContent: {flex: 9},
});
export default IndividualMoment;
diff --git a/src/screens/profile/MomentCommentsScreen.tsx b/src/screens/profile/MomentCommentsScreen.tsx
index 402e5f44..7dfe8ae9 100644
--- a/src/screens/profile/MomentCommentsScreen.tsx
+++ b/src/screens/profile/MomentCommentsScreen.tsx
@@ -48,8 +48,9 @@ const MomentCommentsScreen: React.FC<MomentCommentsScreenProps> = ({route}) => {
React.useState(true);
//Keeps track of the current comments object in focus so that the application knows which comment to post a reply to
- const [commentTapped, setCommentTapped] =
- useState<CommentType | CommentThreadType | undefined>();
+ const [commentTapped, setCommentTapped] = useState<
+ CommentType | CommentThreadType | undefined
+ >();
useEffect(() => {
navigation.setOptions({
diff --git a/src/services/MomentService.ts b/src/services/MomentService.ts
index c66d2786..b837585a 100644
--- a/src/services/MomentService.ts
+++ b/src/services/MomentService.ts
@@ -6,7 +6,7 @@ import {
MOMENT_TAGS_ENDPOINT,
MOMENT_THUMBNAIL_ENDPOINT,
} from '../constants';
-import {MomentTagType, MomentType} from '../types';
+import {MomentPostType, MomentTagType} from '../types';
import {checkImageUploadStatus} from '../utils';
export const postMoment = async (
@@ -86,11 +86,7 @@ export const patchMoment = async (
return false;
};
-export const loadMoments: (
- userId: string,
- token: string,
-) => Promise<MomentType[]> = async (userId, token) => {
- let moments: MomentType[] = [];
+export const loadMoments = async (userId: string, token: string) => {
try {
const response = await fetch(MOMENTS_ENDPOINT + '?user_id=' + userId, {
method: 'GET',
@@ -98,19 +94,14 @@ export const loadMoments: (
Authorization: 'Token ' + token,
},
});
- const status = response.status;
- if (status === 200) {
- const data = await response.json();
- moments = data;
- } else {
- console.log('Could not load moments!');
- return [];
+ if (response.status === 200) {
+ const typedData: MomentPostType[] = await response.json();
+ return typedData;
}
} catch (err) {
console.log(err);
- return [];
}
- return moments;
+ return [];
};
export const deleteMoment = async (momentId: string) => {
diff --git a/src/store/initialStates.ts b/src/store/initialStates.ts
index e2902a2d..92a1e456 100644
--- a/src/store/initialStates.ts
+++ b/src/store/initialStates.ts
@@ -1,14 +1,17 @@
-import {CommentThreadType, UniversityType} from './../types/types';
import {
- MomentType,
NotificationType,
- ProfilePreviewType,
ProfileInfoType,
+ ProfilePreviewType,
ScreenType,
SocialAccountType,
UserType,
UserXType,
} from '../types';
+import {
+ CommentThreadType,
+ MomentPostType,
+ UniversityType,
+} from './../types/types';
export const NO_PROFILE: ProfileInfoType = {
biography: '',
@@ -29,7 +32,7 @@ export const NO_PROFILE: ProfileInfoType = {
is_private: true,
};
-export const EMPTY_MOMENTS_LIST = <MomentType[]>[];
+export const EMPTY_MOMENTS_LIST = <MomentPostType[]>[];
export const EMPTY_NOTIFICATIONS_LIST = <NotificationType[]>[];
diff --git a/src/types/types.ts b/src/types/types.ts
index fd75ab50..171a9ff3 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -119,6 +119,16 @@ export interface MomentType {
thumbnail_url: string;
}
+export interface MomentPostType extends MomentType {
+ comments_count: number;
+ comment_preview: MomentCommentPreviewType;
+}
+
+export interface MomentCommentPreviewType {
+ commenter: ProfilePreviewType;
+ comment: string;
+}
+
export interface MomentTagType {
id: string;
user: ProfilePreviewType;
@@ -172,7 +182,7 @@ export enum ScreenType {
*/
export interface UserXType {
friends: ProfilePreviewType[];
- moments: MomentType[];
+ moments: MomentPostType[];
socialAccounts: Record<string, SocialAccountType>;
momentCategories: string[];
user: UserType;
diff --git a/src/utils/comments.tsx b/src/utils/comments.tsx
index 5c17cefe..910b44e7 100644
--- a/src/utils/comments.tsx
+++ b/src/utils/comments.tsx
@@ -79,8 +79,8 @@ export const renderTextWithMentions: React.FC<RenderProps> = ({
);
};
-export const mentionPartTypes: (style: 'blue' | 'white') => PartType[] = (
- style,
+export const mentionPartTypes: (theme: 'blue' | 'white') => PartType[] = (
+ theme,
) => {
return [
{
@@ -88,17 +88,26 @@ export const mentionPartTypes: (style: 'blue' | 'white') => PartType[] = (
renderSuggestions: (props) => <TaggTypeahead {...props} />,
allowedSpacesCount: 0,
isInsertSpaceAfterMention: true,
- textStyle:
- style === 'blue'
- ? {
- color: TAGG_LIGHT_BLUE,
- top: normalize(3),
- }
- : {
- color: 'white',
- fontWeight: '800',
- top: normalize(7.5),
- },
+ textStyle: _textStyle(theme),
},
];
};
+
+const _textStyle: (theme: 'blue' | 'white') => StyleProp<TextStyle> = (
+ theme,
+) => {
+ switch (theme) {
+ case 'blue':
+ return {
+ color: TAGG_LIGHT_BLUE,
+ top: normalize(3),
+ };
+ case 'white':
+ default:
+ return {
+ color: 'white',
+ fontWeight: '800',
+ top: normalize(3),
+ };
+ }
+};
diff --git a/src/utils/moments.ts b/src/utils/moments.ts
index 90d69519..9e8cc332 100644
--- a/src/utils/moments.ts
+++ b/src/utils/moments.ts
@@ -19,21 +19,21 @@ export const getTimePosted = (date_time: string) => {
// 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');
+ time = difference + 'm ago';
}
// 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');
+ time = difference + 'h ago';
}
// Any number of days
else if (difference >= 24 * 60 * 60 && difference < 24 * 60 * 60 * 3) {
difference = now.diff(datePosted, 'days');
- time = difference + (difference === 1 ? ' day' : ' days');
+ time = difference + 'd ago';
}
// More than 3 days
else if (difference >= 24 * 60 * 60 * 3) {
- time = datePosted.format('MMMM D, YYYY');
+ time = datePosted.format('M-D-YYYY');
}
return time;
};
diff --git a/src/utils/users.ts b/src/utils/users.ts
index 64ad10e9..c1c3b8bc 100644
--- a/src/utils/users.ts
+++ b/src/utils/users.ts
@@ -306,3 +306,21 @@ export const patchProfile = async (
return false;
});
};
+
+/**
+ * Returns the logged-in user's info in ProfilePreviewType from redux store.
+ * @param state the current state of the redux store
+ * @returns logged-in user in ProfilePreviewType
+ */
+export const getLoggedInUserAsProfilePreview: (
+ state: RootState,
+) => ProfilePreviewType = (state) => {
+ const nameSplit = state.user.profile.name.split(' ');
+ return {
+ id: state.user.user.userId,
+ username: state.user.user.username,
+ first_name: nameSplit[0],
+ last_name: nameSplit[1],
+ thumbnail_url: state.user.avatar ?? '', // in full res
+ };
+};