diff options
Diffstat (limited to 'src/components')
-rw-r--r-- | src/components/comments/AddComment.tsx | 96 | ||||
-rw-r--r-- | src/components/comments/CommentsCount.tsx | 58 | ||||
-rw-r--r-- | src/components/comments/index.ts | 1 | ||||
-rw-r--r-- | src/components/common/BottomDrawer.tsx | 2 | ||||
-rw-r--r-- | src/components/common/GenericMoreInfoDrawer.tsx | 14 | ||||
-rw-r--r-- | src/components/moments/IndividualMomentTitleBar.tsx | 12 | ||||
-rw-r--r-- | src/components/moments/MomentCommentPreview.tsx | 97 | ||||
-rw-r--r-- | src/components/moments/MomentPost.tsx | 67 | ||||
-rw-r--r-- | src/components/moments/MomentPostContent.tsx | 157 | ||||
-rw-r--r-- | src/components/moments/MomentPostHeader.tsx | 15 | ||||
-rw-r--r-- | src/components/notifications/Notification.tsx | 11 | ||||
-rw-r--r-- | src/components/notifications/NotificationPill.tsx | 209 | ||||
-rw-r--r-- | src/components/notifications/index.ts | 1 | ||||
-rw-r--r-- | src/components/profile/MomentMoreInfoDrawer.tsx | 90 | ||||
-rw-r--r-- | src/components/profile/ProfileMoreInfoDrawer.tsx | 5 | ||||
-rw-r--r-- | src/components/suggestedPeople/legacy/BadgesDropdown.tsx | 6 |
16 files changed, 577 insertions, 264 deletions
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/common/GenericMoreInfoDrawer.tsx b/src/components/common/GenericMoreInfoDrawer.tsx index 0928ed44..cfc45131 100644 --- a/src/components/common/GenericMoreInfoDrawer.tsx +++ b/src/components/common/GenericMoreInfoDrawer.tsx @@ -3,15 +3,16 @@ import { GestureResponderEvent, StyleSheet, Text, + TextStyle, TouchableOpacity, View, ViewProps, ViewStyle, } from 'react-native'; import {useSafeAreaInsets} from 'react-native-safe-area-context'; -import BottomDrawer from './BottomDrawer'; import {TAGG_LIGHT_BLUE} from '../../constants'; import {normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; +import BottomDrawer from './BottomDrawer'; // conforms the JSX onPress attribute type type OnPressHandler = (event: GestureResponderEvent) => void; @@ -20,13 +21,12 @@ interface GenericMoreInfoDrawerProps extends ViewProps { isOpen: boolean; setIsOpen: (visible: boolean) => void; showIcons: boolean; - textColor: string; // An array of title, onPressHandler, and icon component - buttons: [string, OnPressHandler, JSX.Element?][]; + buttons: [string, OnPressHandler, JSX.Element?, TextStyle?][]; } const GenericMoreInfoDrawer: React.FC<GenericMoreInfoDrawerProps> = (props) => { - const {buttons, showIcons, textColor} = props; + const {buttons, showIcons} = props; // each button is 80px high, cancel button is always there const initialSnapPosition = (buttons.length + 1) * 80 + useSafeAreaInsets().bottom; @@ -44,13 +44,11 @@ const GenericMoreInfoDrawer: React.FC<GenericMoreInfoDrawerProps> = (props) => { showHeader={false} initialSnapPosition={initialSnapPosition}> <View style={styles.panel}> - {buttons.map(([title, action, icon], index) => ( + {buttons.map(([title, action, icon, textStyle], index) => ( <View key={index}> <TouchableOpacity style={panelButtonStyle} onPress={action}> {showIcons && <View style={styles.icon}>{icon}</View>} - <Text style={[styles.panelButtonTitle, {color: textColor}]}> - {title} - </Text> + <Text style={[styles.panelButtonTitle, textStyle]}>{title}</Text> </TouchableOpacity> <View style={styles.divider} /> </View> 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 7149a5b4..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,16 +34,13 @@ 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(); + loadMomentTags(moment.moment_id).then((response) => { + setTags(response ? response : []); + }); }, []); /* @@ -71,52 +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} - momentId={item.moment_id} - style={styles.postHeader} momentTagId={momentTagId} removeTag={removeTag} + moment={moment} + tags={tags} /> <MomentPostContent style={styles.postContent} - momentId={item.moment_id} - caption={item.caption} - pathHash={item.moment_url} - dateTime={item.date_created} + moment={moment} screenType={screenType} 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 4a1f3894..aca2999c 100644 --- a/src/components/moments/MomentPostContent.tsx +++ b/src/components/moments/MomentPostContent.tsx @@ -1,77 +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, 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; - momentId: string; - caption: string; - pathHash: string; - dateTime: string; + moment: MomentPostType; momentTags: MomentTagType[]; + index: number; } const MomentPostContent: React.FC<MomentPostContentProps> = ({ screenType, - momentId, - caption, - pathHash, - dateTime, + moment, style, momentTags, + index, }) => { + const [tags, setTags] = useState<MomentTagType[]>(momentTags); const state: RootState = useStore().getState(); const navigation = useNavigation(); const dispatch = useDispatch(); - const [elapsedTime, setElapsedTime] = useState(''); - const [comments_count, setCommentsCount] = useState(''); - const [tags, setTags] = useState<MomentTagType[]>(momentTags); 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(momentId, false); - setCommentsCount(count); - }; - setElapsedTime(getTimePosted(dateTime)); - fetchCommentsCount(); - }, [dateTime, momentId]); - - 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,76 +86,95 @@ const MomentPostContent: React.FC<MomentPostContentProps> = ({ <Image ref={imageRef} style={styles.image} - source={{uri: pathHash}} + source={{uri: moment.moment_url}} resizeMode={'cover'} /> {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={momentId} - screenType={screenType} - /> - <Text style={styles.text}>{elapsedTime}</Text> - </View> - {renderTextWithMentions({ - value: 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 dc6a3cd9..5f26951a 100644 --- a/src/components/moments/MomentPostHeader.tsx +++ b/src/components/moments/MomentPostHeader.tsx @@ -10,7 +10,7 @@ import { import {useDispatch, useSelector, useStore} from 'react-redux'; import {loadUserMoments} from '../../store/actions'; import {RootState} from '../../store/rootReducer'; -import {ScreenType} from '../../types'; +import {MomentTagType, MomentType, ScreenType} from '../../types'; import {fetchUserX, userXInStore} from '../../utils'; import {MomentMoreInfoDrawer} from '../profile'; import TaggAvatar from '../profile/TaggAvatar'; @@ -19,19 +19,21 @@ interface MomentPostHeaderProps extends ViewProps { userXId?: string; screenType: ScreenType; username: string; - momentId: string; momentTagId: string; removeTag: () => Promise<void>; + moment: MomentType; + tags: MomentTagType[]; } const MomentPostHeader: React.FC<MomentPostHeaderProps> = ({ userXId, screenType, username, - momentId, style, momentTagId, removeTag, + moment, + tags, }) => { const [drawerVisible, setDrawerVisible] = useState(false); const dispatch = useDispatch(); @@ -62,20 +64,23 @@ const MomentPostHeader: React.FC<MomentPostHeaderProps> = ({ style={styles.avatar} userXId={userXId} screenType={screenType} + editable={false} /> <Text style={styles.headerText}>{username}</Text> </TouchableOpacity> <MomentMoreInfoDrawer isOpen={drawerVisible} setIsOpen={setDrawerVisible} - momentId={momentId} isOwnProfile={isOwnProfile} momentTagId={momentTagId} removeTag={removeTag} dismissScreenAndUpdate={() => { dispatch(loadUserMoments(loggedInUserId)); - navigation.pop(); + navigation.goBack(); }} + screenType={screenType} + moment={moment} + tags={tags} /> </View> ); diff --git a/src/components/notifications/Notification.tsx b/src/components/notifications/Notification.tsx index 3f9cc56a..fd1b11ac 100644 --- a/src/components/notifications/Notification.tsx +++ b/src/components/notifications/Notification.tsx @@ -2,9 +2,7 @@ import {useNavigation} from '@react-navigation/native'; import React, {useEffect, useState} from 'react'; import {Alert, Image, StyleSheet, Text, View} from 'react-native'; import {TouchableWithoutFeedback} from 'react-native-gesture-handler'; -import LinearGradient from 'react-native-linear-gradient'; import {useDispatch, useStore} from 'react-redux'; -import {BACKGROUND_GRADIENT_MAP} from '../../constants'; import {ERROR_DELETED_OBJECT} from '../../constants/strings'; import {loadImageFromURL} from '../../services'; import { @@ -49,7 +47,6 @@ const Notification: React.FC<NotificationProps> = (props) => { verbage, notification_type, notification_object, - unread, timestamp, }, screenType, @@ -323,13 +320,7 @@ const Notification: React.FC<NotificationProps> = (props) => { </View> ); - return unread ? ( - <LinearGradient colors={BACKGROUND_GRADIENT_MAP[2]} useAngle angle={90}> - {renderContent()} - </LinearGradient> - ) : ( - renderContent() - ); + return renderContent(); }; const styles = StyleSheet.create({ diff --git a/src/components/notifications/NotificationPill.tsx b/src/components/notifications/NotificationPill.tsx new file mode 100644 index 00000000..525cd7fa --- /dev/null +++ b/src/components/notifications/NotificationPill.tsx @@ -0,0 +1,209 @@ +import React, {useEffect, useState, useRef} from 'react'; +import {Image, StyleSheet, Text, View} from 'react-native'; +import LinearGradient from 'react-native-linear-gradient'; +import {SCREEN_WIDTH, isIPhoneX, numberWithCommas} from '../../utils'; +import { + NOTIFICATION_ICON_GRADIENT, + CHIN_HEIGHT, + NAV_BAR_HEIGHT, +} from '../../constants'; +import {getNotificationsUnreadCount} from '../../services'; +import {normalize} from 'react-native-elements'; +import PillIcon4 from '../../assets/images/Group 479.svg'; + +interface NotificationPillProps { + showIcon: boolean; +} + +export const NotificationPill: React.FC<NotificationPillProps> = ({ + showIcon, +}) => { + const [iconStart, setIconStart] = useState<number[]>([0, -100]); + const [tipStart, setTipStart] = useState<number[]>([0, -100]); + const [notificationSets, setNotificationSets] = useState<{ + CMT?: number; + FRD_REQ?: number; + P_VIEW?: number; + MOM_TAG?: number; + }>({}); + const [timeCount, setTimeCount] = useState<boolean>(false); + const [timeOut, setTimeOut] = useState<boolean>(false); + const iconRef = useRef(null); + const tipRef = useRef(null); + const pillTip = require('../../assets/images/purple-tip.png'); + + const navBarPos = 20; + + // If there are notifications, determines the size of the pill + // and sets points for correct placement + useEffect(() => { + setTimeout(() => { + if (iconRef.current) { + iconRef.current.measure( + ( + _fx: number, + _fy: number, + width: number, + height: number, + _px: number, + _py: number, + ) => { + if (tipRef.current) { + tipRef.current.measure( + ( + __fx: number, + __fy: number, + width2: number, + __height: number, + __px: number, + __py: number, + ) => { + const x = SCREEN_WIDTH / 2 - width / 2; + const y = isIPhoneX() + ? CHIN_HEIGHT + NAV_BAR_HEIGHT + navBarPos + : NAV_BAR_HEIGHT + navBarPos; + setIconStart([x, y]); + setTipStart([width / 2 - width2 / 2, height - 1]); + setTimeCount(true); + }, + ); + } + }, + ); + } else { + } + }, 100); + }, [notificationSets, iconRef, tipRef]); + + // Used so that pill disappears after 10 seconds + useEffect(() => { + if (timeCount) { + setTimeout(() => { + setTimeOut(true); + }, 10000); + } + }, [timeCount]); + + // Gets data from backend to check for unreads + useEffect(() => { + const getCount = async () => { + const data = await getNotificationsUnreadCount(); + setTimeout(() => { + if (data) { + setNotificationSets(data); + } + }, 100); + }; + + getCount(); + }, []); + + return ( + <> + {notificationSets && + Object.keys(notificationSets).length !== 0 && + showIcon && + !timeOut && ( + <View + style={[ + styles.purpleContainer, + {bottom: iconStart[1], left: iconStart[0]}, + ]} + ref={iconRef}> + <LinearGradient + colors={NOTIFICATION_ICON_GRADIENT} + style={styles.iconPurple}> + {notificationSets.CMT && ( + <> + <Image + source={require('../../assets/images/pill-icon-1.png')} + style={styles.indicationIcon} + /> + <Text style={styles.text}> + {numberWithCommas(notificationSets.CMT)} + </Text> + </> + )} + {notificationSets.FRD_REQ && ( + <> + <Image + source={require('../../assets/images/pill-icon-2.png')} + style={styles.indicationIcon} + /> + <Text style={styles.text}> + {numberWithCommas(notificationSets.FRD_REQ)} + </Text> + </> + )} + {notificationSets.P_VIEW && ( + <> + <Image + source={require('../../assets/images/pill-icon-3.png')} + style={styles.indicationIcon} + /> + <Text style={styles.text}> + {numberWithCommas(notificationSets.P_VIEW)} + </Text> + </> + )} + {notificationSets.MOM_TAG && ( + <> + <PillIcon4 style={styles.indicationIcon} /> + <Text style={styles.text}> + {numberWithCommas(notificationSets.MOM_TAG)} + </Text> + </> + )} + </LinearGradient> + <Image + style={[styles.tip, {top: tipStart[1], left: tipStart[0]}]} + source={pillTip} + ref={tipRef} + /> + </View> + )} + </> + ); +}; + +const styles = StyleSheet.create({ + purpleContainer: { + flex: 1, + justifyContent: 'center', + position: 'absolute', + zIndex: 999, + }, + iconPurple: { + padding: 5, + borderRadius: 15, + flex: 1, + flexDirection: 'row', + alignItems: 'center', + }, + text: { + margin: 2, + color: 'white', + fontSize: normalize(10), + justifyContent: 'center', + alignItems: 'center', + marginRight: 5, + }, + tip: { + position: 'absolute', + zIndex: 999, + height: 12, + flex: 1, + resizeMode: 'contain', + }, + indicationIcon: { + height: 14, + width: 14, + margin: 2, + marginLeft: 5, + }, + svgIndicationIcon: { + height: 14, + width: 14, + margin: 3, + }, +}); diff --git a/src/components/notifications/index.ts b/src/components/notifications/index.ts index 733b56f1..077c26a4 100644 --- a/src/components/notifications/index.ts +++ b/src/components/notifications/index.ts @@ -1,2 +1,3 @@ export {default as Notification} from './Notification'; export {InviteFriendsPrompt} from './NotificationPrompts'; +export {NotificationPill} from './NotificationPill'; diff --git a/src/components/profile/MomentMoreInfoDrawer.tsx b/src/components/profile/MomentMoreInfoDrawer.tsx index 1265497e..a796ffd8 100644 --- a/src/components/profile/MomentMoreInfoDrawer.tsx +++ b/src/components/profile/MomentMoreInfoDrawer.tsx @@ -1,44 +1,58 @@ +import {useNavigation} from '@react-navigation/core'; import React, {useEffect, useState} from 'react'; import { Alert, GestureResponderEvent, StyleSheet, + TextStyle, TouchableOpacity, ViewProps, } from 'react-native'; import MoreIcon from '../../assets/icons/more_horiz-24px.svg'; import {ERROR_DELETE_MOMENT, MOMENT_DELETED_MSG} from '../../constants/strings'; import {deleteMoment, sendReport} from '../../services'; +import {MomentTagType, MomentType, ScreenType} from '../../types/types'; import {GenericMoreInfoDrawer} from '../common'; enum MomentDrawerOptions { DeleteMoment = 'Delete Moment', ReportIssue = 'Report an Issue', RemoveTag = 'Remove yourself from moment', + EditMoment = 'Edit Moment', } interface MomentMoreInfoDrawerProps extends ViewProps { isOpen: boolean; setIsOpen: (visible: boolean) => void; - momentId: string; isOwnProfile: boolean; momentTagId: string; removeTag: () => Promise<void>; dismissScreenAndUpdate: () => void; + screenType: ScreenType; + moment: MomentType; + tags: MomentTagType[]; } const MomentMoreInfoDrawer: React.FC<MomentMoreInfoDrawerProps> = (props) => { const { - momentId, setIsOpen, isOwnProfile, dismissScreenAndUpdate, momentTagId, removeTag, + screenType, + moment, + tags, } = props; + const navigation = useNavigation(); + + const [drawerButtons, setDrawerButtons] = useState< + [string, (event: GestureResponderEvent) => void, JSX.Element?, TextStyle?][] + >([]); + const handleDeleteMoment = async () => { setIsOpen(false); - deleteMoment(momentId).then((success) => { + deleteMoment(moment.moment_id).then((success) => { if (success) { // set time out for UI transitions setTimeout(() => { @@ -88,7 +102,8 @@ const MomentMoreInfoDrawer: React.FC<MomentMoreInfoDrawerProps> = (props) => { [ { text: 'Mark as inappropriate', - onPress: () => sendReport(momentId, 'Mark as inappropriate'), + onPress: () => + sendReport(moment.moment_id, 'Mark as inappropriate'), }, { text: 'Cancel', @@ -96,7 +111,7 @@ const MomentMoreInfoDrawer: React.FC<MomentMoreInfoDrawerProps> = (props) => { }, { text: 'Mark as abusive', - onPress: () => sendReport(momentId, 'Mark as abusive'), + onPress: () => sendReport(moment.moment_id, 'Mark as abusive'), }, ], {cancelable: false}, @@ -104,42 +119,52 @@ const MomentMoreInfoDrawer: React.FC<MomentMoreInfoDrawerProps> = (props) => { }, 500); }; - const [drawerButtons, setDrawerButtons] = useState< - [string, (event: GestureResponderEvent) => void, JSX.Element?][] - >([ - isOwnProfile - ? [MomentDrawerOptions.DeleteMoment, handleDeleteMoment] - : [MomentDrawerOptions.ReportIssue, handleReportMoment], - ]); + const handleEditMoment = async () => { + setIsOpen(false); + navigation.navigate('CaptionScreen', { + screenType: screenType, + selectedTags: tags, + moment: moment, + }); + }; /* * Update bottom drawer options to contain/not contain 'remove tag' option */ useEffect(() => { - const setupBottomDrawer = () => { - const present = drawerButtons.findIndex( - (button) => button[0] === MomentDrawerOptions.RemoveTag, - ); - /* - * If user is not tagged but button is present, remove button from bottom drawer - * If user is tagged but button is not present, add button to bottom drawer - */ - if (momentTagId !== '' && present === -1) { - const localDrawerButtons = drawerButtons; - localDrawerButtons.push([ + let newButtons: [ + string, + (event: GestureResponderEvent) => void, + JSX.Element?, + TextStyle?, + ][] = []; + if (!isOwnProfile) { + newButtons.push([ + MomentDrawerOptions.ReportIssue, + handleReportMoment, + undefined, + {color: 'red'}, + ]); + // should we have the "delete moment" option? + if (momentTagId !== '') { + newButtons.push([ MomentDrawerOptions.RemoveTag, handleRemoveTag, + undefined, + {color: 'red'}, ]); - setDrawerButtons(localDrawerButtons); - } else if (momentTagId === '' && present !== -1) { - const filteredButtons = drawerButtons.filter( - (button) => button[0] !== MomentDrawerOptions.RemoveTag, - ); - setDrawerButtons(filteredButtons); } - }; - setupBottomDrawer(); - }, [momentTagId]); + } else { + newButtons.push([ + MomentDrawerOptions.DeleteMoment, + handleDeleteMoment, + undefined, + {color: 'red'}, + ]); + newButtons.push([MomentDrawerOptions.EditMoment, handleEditMoment]); + } + setDrawerButtons(newButtons); + }, [tags, momentTagId]); return ( <> @@ -153,7 +178,6 @@ const MomentMoreInfoDrawer: React.FC<MomentMoreInfoDrawerProps> = (props) => { <GenericMoreInfoDrawer {...props} showIcons={false} - textColor={'red'} buttons={drawerButtons} /> </> diff --git a/src/components/profile/ProfileMoreInfoDrawer.tsx b/src/components/profile/ProfileMoreInfoDrawer.tsx index ecc45211..656f81bb 100644 --- a/src/components/profile/ProfileMoreInfoDrawer.tsx +++ b/src/components/profile/ProfileMoreInfoDrawer.tsx @@ -55,12 +55,12 @@ const ProfileMoreInfoDrawer: React.FC<ProfileMoreInfoDrawerProps> = (props) => { <GenericMoreInfoDrawer {...props} showIcons={false} - textColor={'red'} buttons={[ [ (isBlocked ? 'Unblock' : 'Block') + ` ${userXName}`, onBlockUnblock, undefined, + {color: 'red'}, ], ]} /> @@ -68,7 +68,6 @@ const ProfileMoreInfoDrawer: React.FC<ProfileMoreInfoDrawerProps> = (props) => { <GenericMoreInfoDrawer {...props} showIcons={true} - textColor={'black'} buttons={[ [ 'Settings', @@ -77,6 +76,7 @@ const ProfileMoreInfoDrawer: React.FC<ProfileMoreInfoDrawerProps> = (props) => { source={require('../../assets/images/settings/settings.png')} style={styles.image} />, + {color: 'black'}, ], [ 'Edit Profile', @@ -85,6 +85,7 @@ const ProfileMoreInfoDrawer: React.FC<ProfileMoreInfoDrawerProps> = (props) => { source={require('../../assets/images/settings/edit-profile.png')} style={styles.image} />, + {color: 'black'}, ], ]} /> 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(); } } |