diff options
26 files changed, 1023 insertions, 367 deletions
diff --git a/ios/Podfile b/ios/Podfile index 4eca4100..d8324a6d 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -33,4 +33,4 @@ end # add the Firebase pod for Google Analytics pod 'Firebase/Analytics' # add pods for any other desired Firebase products -# https://firebase.google.com/docs/ios/setup#available-pods
\ No newline at end of file +# https://firebase.google.com/docs/ios/setup#available-pods diff --git a/package.json b/package.json index 399a40ea..09ed6fc5 100644 --- a/package.json +++ b/package.json @@ -47,9 +47,11 @@ "react-native-hyperlink": "^0.0.19", "react-native-image-crop-picker": "^0.36.0", "react-native-image-picker": "^4.0.4", + "react-native-image-pan-zoom": "^2.1.12", "react-native-image-resizer": "^1.4.4", "react-native-inappbrowser-reborn": "^3.5.0", "react-native-linear-gradient": "^2.5.6", + "react-native-photo-manipulator": "^1.2.4", "react-native-picker-select": "^7.0.0", "react-native-push-notifications": "^3.0.10", "react-native-reanimated": "2.0.0-rc.0", @@ -108,4 +110,4 @@ "./node_modules/react-native-gesture-handler/jestSetup.js" ] } -} +}
\ No newline at end of file diff --git a/src/components/comments/AddComment.tsx b/src/components/comments/AddComment.tsx index 9667046c..8a4ec082 100644 --- a/src/components/comments/AddComment.tsx +++ b/src/components/comments/AddComment.tsx @@ -7,19 +7,16 @@ import { TextInput, View, } from 'react-native'; -import {TouchableOpacity} from 'react-native-gesture-handler'; -import {useDispatch, useSelector} from 'react-redux'; -import UpArrowIcon from '../../assets/icons/up_arrow.svg'; +import {useDispatch} from 'react-redux'; import {TAGG_LIGHT_BLUE} from '../../constants'; import {CommentContext} from '../../screens/profile/MomentCommentsScreen'; import {postComment} from '../../services'; import {updateReplyPosted} from '../../store/actions'; -import {RootState} from '../../store/rootreducer'; import {CommentThreadType, CommentType} from '../../types'; -import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; +import {SCREEN_HEIGHT, SCREEN_WIDTH, normalize} from '../../utils'; import {mentionPartTypes} from '../../utils/comments'; -import {Avatar} from '../common'; -import {MentionInputControlled} from './MentionInputControlled'; +import {CommentTextField} from './CommentTextField'; +import MentionInputControlled from './MentionInputControlled'; export interface AddCommentProps { momentId: string; @@ -38,12 +35,11 @@ const AddComment: React.FC<AddCommentProps> = ({ isKeyboardAvoiding = true, theme = 'white', }) => { - const {setShouldUpdateAllComments = () => null, commentTapped} = + const {setShouldUpdateAllComments, commentTapped} = useContext(CommentContext); const [inReplyToMention, setInReplyToMention] = useState(''); const [comment, setComment] = useState(''); const [keyboardVisible, setKeyboardVisible] = useState(false); - const {avatar} = useSelector((state: RootState) => state.user); const dispatch = useDispatch(); const ref = useRef<TextInput>(null); const isReplyingToComment = @@ -120,7 +116,6 @@ const AddComment: React.FC<AddCommentProps> = ({ keyboardVisible && theme !== 'dark' ? styles.whiteBackround : {}, ]}> <View style={styles.textContainer}> - <Avatar style={styles.avatar} uri={avatar} /> <MentionInputControlled containerStyle={styles.text} placeholderTextColor={theme === 'dark' ? '#828282' : undefined} @@ -134,25 +129,17 @@ const AddComment: React.FC<AddCommentProps> = ({ ); }} inputRef={ref} - partTypes={mentionPartTypes('blue')} + partTypes={mentionPartTypes('blue', 'comment')} + addComment={addComment} + NewText={CommentTextField} + theme={theme} + keyboardVisible={keyboardVisible} + comment={comment} /> - {(theme === 'white' || (theme === 'dark' && keyboardVisible)) && ( - <View style={styles.submitButton}> - <TouchableOpacity - style={ - comment === '' - ? [styles.submitButton, styles.greyButton] - : styles.submitButton - } - disabled={comment === ''} - onPress={addComment}> - <UpArrowIcon width={35} height={35} color={'white'} /> - </TouchableOpacity> - </View> - )} </View> </View> ); + return isKeyboardAvoiding ? ( <KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : 'height'} @@ -176,17 +163,15 @@ const styles = StyleSheet.create({ }, textContainer: { width: '95%', - flexDirection: 'row', backgroundColor: '#e8e8e8', alignItems: 'center', justifyContent: 'space-between', margin: '3%', borderRadius: 25, + height: normalize(45), }, text: { flex: 1, - padding: '1%', - marginHorizontal: '1%', maxHeight: 100, }, avatar: { @@ -209,9 +194,6 @@ const styles = StyleSheet.create({ marginVertical: '2%', alignSelf: 'flex-end', }, - greyButton: { - backgroundColor: 'grey', - }, whiteBackround: { backgroundColor: '#fff', }, diff --git a/src/components/comments/CommentTextField.tsx b/src/components/comments/CommentTextField.tsx new file mode 100644 index 00000000..6e92329c --- /dev/null +++ b/src/components/comments/CommentTextField.tsx @@ -0,0 +1,164 @@ +import React, {FC, ReactFragment} from 'react'; +import { + NativeSyntheticEvent, + StyleSheet, + StyleProp, + Text, + TextInput, + TextInputSelectionChangeEventData, + TouchableOpacity, + View, + ViewStyle, +} from 'react-native'; +import {useSelector} from 'react-redux'; +import {TAGG_LIGHT_BLUE} from '../../constants'; +import {RootState} from '../../store/rootReducer'; +import { + Part, + PartType, + MentionPartType, +} from 'react-native-controlled-mentions/dist/types'; +import { + defaultMentionTextStyle, + isMentionPartType, +} from 'react-native-controlled-mentions/dist/utils'; +import {Avatar} from '../common'; +import {normalize} from '../../utils'; + +import UpArrowIcon from '../../assets/icons/up_arrow.svg'; + +type CommentTextFieldProps = { + containerStyle: StyleProp<ViewStyle>; + validateInput: any; + keyboardText: string; + partTypes: PartType[]; + renderMentionSuggestions: (mentionType: MentionPartType) => ReactFragment; + handleTextInputRef: (ref: TextInput) => null; + onChangeInput: (changedText: string) => null; + handleSelectionChange: ( + event: NativeSyntheticEvent<TextInputSelectionChangeEventData>, + ) => null; + parts: Part[]; + addComment: () => any; + theme?: 'dark' | 'white'; + keyboardVisible?: boolean; + comment?: string; +}; + +const CommentTextField: FC<CommentTextFieldProps> = ({ + containerStyle, + validateInput, + keyboardText, + partTypes, + renderMentionSuggestions, + handleTextInputRef, + onChangeInput, + handleSelectionChange, + parts, + addComment, + theme = 'white', + keyboardVisible = true, + comment = '', + ...textInputProps +}) => { + const {avatar} = useSelector((state: RootState) => state.user); + + return ( + <View style={containerStyle}> + {validateInput(keyboardText) + ? ( + partTypes.filter( + (one) => + isMentionPartType(one) && + one.renderSuggestions != null && + !one.isBottomMentionSuggestionsRender, + ) as MentionPartType[] + ).map(renderMentionSuggestions) + : null} + + <View style={styles.containerStyle}> + <Avatar style={styles.avatar} uri={avatar} /> + <TextInput + multiline + {...textInputProps} + ref={handleTextInputRef} + onChangeText={onChangeInput} + onSelectionChange={handleSelectionChange} + style={styles.text}> + <Text> + {parts.map(({text, partType, data}, index) => + partType ? ( + <Text + key={`${index}-${data?.trigger ?? 'pattern'}`} + style={partType.textStyle ?? defaultMentionTextStyle}> + {text} + </Text> + ) : ( + <Text key={index}>{text}</Text> + ), + )} + </Text> + </TextInput> + {(theme === 'white' || (theme === 'dark' && keyboardVisible)) && ( + <View style={styles.submitButton}> + <TouchableOpacity + style={ + comment === '' + ? [styles.submitButton, styles.greyButton] + : styles.submitButton + } + disabled={comment === ''} + onPress={addComment}> + <UpArrowIcon width={35} height={35} color={'white'} /> + </TouchableOpacity> + </View> + )} + </View> + + {validateInput(keyboardText) && + ( + partTypes.filter( + (one) => + isMentionPartType(one) && + one.renderSuggestions != null && + one.isBottomMentionSuggestionsRender, + ) as MentionPartType[] + ).map(renderMentionSuggestions)} + </View> + ); +}; + +const styles = StyleSheet.create({ + avatar: { + height: 35, + width: 35, + borderRadius: 30, + marginRight: 10, + marginLeft: '3%', + marginVertical: '2%', + }, + containerStyle: { + flexDirection: 'row', + alignSelf: 'center', + alignItems: 'center', + justifyContent: 'center', + height: normalize(45), + }, + greyButton: { + backgroundColor: 'grey', + }, + submitButton: { + height: 35, + width: 35, + backgroundColor: TAGG_LIGHT_BLUE, + borderRadius: 999, + justifyContent: 'center', + alignItems: 'center', + marginRight: '3%', + marginVertical: '2%', + alignSelf: 'flex-end', + }, + text: {flex: 1}, +}); + +export {CommentTextField}; diff --git a/src/components/comments/CommentsCount.tsx b/src/components/comments/CommentsCount.tsx new file mode 100644 index 00000000..90514193 --- /dev/null +++ b/src/components/comments/CommentsCount.tsx @@ -0,0 +1,48 @@ +import {useNavigation} from '@react-navigation/core'; +import React from 'react'; +import {StyleSheet, Text} from 'react-native'; +import {TouchableOpacity} from 'react-native-gesture-handler'; +import CommentsIcon from '../../assets/icons/moment-comment-icon.svg'; +import {MomentPostType, ScreenType} from '../../types'; +import {normalize} from '../../utils'; + +interface CommentsCountProps { + moment: MomentPostType; + screenType: ScreenType; +} + +const CommentsCount: React.FC<CommentsCountProps> = ({moment, screenType}) => { + const navigation = useNavigation(); + return ( + <TouchableOpacity + style={styles.countContainer} + onPress={() => + navigation.navigate('MomentCommentsScreen', { + moment_id: moment.moment_id, + screenType, + }) + }> + <CommentsIcon width={25} height={25} /> + <Text style={styles.count}>{moment.comments_count}</Text> + </TouchableOpacity> + ); +}; + +const styles = StyleSheet.create({ + countContainer: { + minWidth: 50, + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + }, + count: { + fontWeight: '500', + fontSize: normalize(11), + lineHeight: normalize(13), + letterSpacing: normalize(0.05), + textAlign: 'center', + color: 'white', + marginTop: normalize(5), + }, +}); +export default CommentsCount; diff --git a/src/components/comments/MentionInputControlled.tsx b/src/components/comments/MentionInputControlled.tsx index 2fd2b41d..0965e318 100644 --- a/src/components/comments/MentionInputControlled.tsx +++ b/src/components/comments/MentionInputControlled.tsx @@ -1,13 +1,23 @@ -import React, {FC, MutableRefObject, useMemo, useRef, useState} from 'react'; +import React, { + FC, + MutableRefObject, + Ref, + useMemo, + useRef, + useState, +} from 'react'; import { NativeSyntheticEvent, + StyleProp, Text, TextInput, + TextInputProps, TextInputSelectionChangeEventData, View, + ViewStyle, } from 'react-native'; import { - MentionInputProps, + PatternPartType, MentionPartType, Suggestion, } from 'react-native-controlled-mentions/dist/types'; @@ -20,7 +30,30 @@ import { parseValue, } from 'react-native-controlled-mentions/dist/utils'; -const MentionInputControlled: FC<MentionInputProps> = ({ +type PartType = MentionPartType | PatternPartType; + +type MentionInputControlledProps = Omit<TextInputProps, 'onChange'> & { + value: string; + onChange: (value: string) => any; + + partTypes?: PartType[]; + + inputRef?: Ref<TextInput>; + + containerStyle?: StyleProp<ViewStyle>; + + addComment?: () => any | null; + + NewText?: FC<any>; + + theme?: 'dark' | 'white'; + + keyboardVisible?: boolean; + + comment?: string; +}; + +const MentionInputControlled: FC<MentionInputControlledProps> = ({ value, onChange, @@ -32,6 +65,16 @@ const MentionInputControlled: FC<MentionInputProps> = ({ onSelectionChange, + addComment, + + NewText, + + theme = 'white', + + keyboardVisible = true, + + comment = '', + ...textInputProps }) => { const textInput = useRef<TextInput | null>(null); @@ -147,7 +190,24 @@ const MentionInputControlled: FC<MentionInputProps> = ({ return validRegex().test(testString); }; - return ( + return NewText ? ( + <NewText + {...textInputProps} + containerStyle={containerStyle} + validateInput={validateInput} + keyboardText={keyboardText} + partTypes={partTypes} + renderMentionSuggestions={renderMentionSuggestions} + handleTextInputRef={handleTextInputRef} + onChangeInput={onChangeInput} + handleSelectionChange={handleSelectionChange} + parts={parts} + addComment={addComment} + theme={theme} + keyboardVisible={keyboardVisible} + comment={comment} + /> + ) : ( <View style={containerStyle}> {validateInput(keyboardText) ? ( @@ -195,4 +255,4 @@ const MentionInputControlled: FC<MentionInputProps> = ({ ); }; -export {MentionInputControlled}; +export default MentionInputControlled; diff --git a/src/components/comments/ZoomInCropper.tsx b/src/components/comments/ZoomInCropper.tsx new file mode 100644 index 00000000..bca4e599 --- /dev/null +++ b/src/components/comments/ZoomInCropper.tsx @@ -0,0 +1,201 @@ +import {RouteProp} from '@react-navigation/core'; +import {useFocusEffect} from '@react-navigation/native'; +import {StackNavigationProp} from '@react-navigation/stack'; +import {default as React, useCallback, useEffect, useState} from 'react'; +import {Image, StyleSheet, TouchableOpacity} from 'react-native'; +import {normalize} from 'react-native-elements'; +import ImageZoom, {IOnMove} from 'react-native-image-pan-zoom'; +import PhotoManipulator from 'react-native-photo-manipulator'; +import CloseIcon from '../../assets/ionicons/close-outline.svg'; +import {MainStackParams} from '../../routes'; +import {HeaderHeight, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; +import {TaggSquareButton} from '../common'; + +type ZoomInCropperRouteProps = RouteProp<MainStackParams, 'ZoomInCropper'>; +type ZoomInCropperNavigationProps = StackNavigationProp< + MainStackParams, + 'ZoomInCropper' +>; +interface ZoomInCropperProps { + route: ZoomInCropperRouteProps; + navigation: ZoomInCropperNavigationProps; +} + +export const ZoomInCropper: React.FC<ZoomInCropperProps> = ({ + route, + navigation, +}) => { + const {screenType, title, media} = route.params; + const [aspectRatio, setAspectRatio] = useState<number>(1); + + // Stores the coordinates of the cropped image + const [x0, setX0] = useState<number>(); + const [x1, setX1] = useState<number>(); + const [y0, setY0] = useState<number>(); + const [y1, setY1] = useState<number>(); + + // Removes bottom navigation bar on current screen and add it back when navigating away + useFocusEffect( + useCallback(() => { + navigation.dangerouslyGetParent()?.setOptions({ + tabBarVisible: false, + }); + return () => { + navigation.dangerouslyGetParent()?.setOptions({ + tabBarVisible: true, + }); + }; + }, [navigation]), + ); + + // Setting original aspect ratio of image + useEffect(() => { + if (media.uri) { + Image.getSize( + media.uri, + (w, h) => { + setAspectRatio(w / h); + }, + (err) => console.log(err), + ); + } + }, []); + + // Crops original image based of (x0, y0) and (x1, y1) coordinates + const handleNext = () => { + if ( + x0 !== undefined && + x1 !== undefined && + y0 !== undefined && + y1 !== undefined + ) { + PhotoManipulator.crop(media.uri, { + x: x0, + y: y1, + width: Math.abs(x0 - x1), + height: Math.abs(y0 - y1), + }) + .then((croppedURL) => { + navigation.navigate('CaptionScreen', { + screenType, + title: title, + media: { + filename: media.filename, + uri: croppedURL, + isVideo: false, + }, + }); + }) + .catch((err) => console.log('err: ', err)); + } else if ( + x0 === undefined && + x1 === undefined && + y0 === undefined && + y1 === undefined + ) { + navigation.navigate('CaptionScreen', { + screenType, + title: title, + media, + }); + } + }; + + /* Records (x0, y0) and (x1, y1) coordinates used later for cropping, + * based on(x, y) - the center of the image and scale of zoom + */ + const onMove = (position: IOnMove) => { + Image.getSize( + media.uri, + (w, h) => { + const x = position.positionX; + const y = position.positionY; + const scale = position.scale; + const screen_ratio = SCREEN_HEIGHT / SCREEN_WIDTH; + let tempx0 = w / 2 - x * (w / SCREEN_WIDTH) - w / 2 / scale; + let tempx1 = w / 2 - x * (w / SCREEN_WIDTH) + w / 2 / scale; + if (tempx0 < 0) { + tempx0 = 0; + } + if (tempx1 > w) { + tempx1 = w; + } + const x_distance = Math.abs(tempx1 - tempx0); + const y_distance = screen_ratio * x_distance; + let tempy0 = h / 2 - y * (h / SCREEN_HEIGHT) + y_distance / 2; + let tempy1 = h / 2 - y * (h / SCREEN_HEIGHT) - y_distance / 2; + if (tempy0 > h) { + tempy0 = h; + } + if (tempy1 < 0) { + tempy1 = 0; + } + setX0(tempx0); + setX1(tempx1); + setY0(tempy0); + setY1(tempy1); + }, + (err) => console.log(err), + ); + }; + + return ( + <> + <TouchableOpacity + style={styles.closeButton} + onPress={() => navigation.goBack()}> + <CloseIcon height={25} width={25} color={'white'} /> + </TouchableOpacity> + <ImageZoom + style={styles.zoomView} + cropWidth={SCREEN_WIDTH} + cropHeight={SCREEN_HEIGHT} + imageWidth={SCREEN_WIDTH} + imageHeight={SCREEN_WIDTH / aspectRatio} + onMove={onMove}> + <Image + style={{width: SCREEN_WIDTH, height: SCREEN_WIDTH / aspectRatio}} + source={{ + uri: media.uri, + }} + /> + </ImageZoom> + <TaggSquareButton + onPress={handleNext} + title={'Next'} + buttonStyle={'normal'} + buttonColor={'blue'} + labelColor={'white'} + style={styles.button} + labelStyle={styles.buttonLabel} + /> + </> + ); +}; + +const styles = StyleSheet.create({ + closeButton: { + position: 'absolute', + top: 0, + paddingTop: HeaderHeight, + zIndex: 1, + marginLeft: '5%', + }, + button: { + zIndex: 1, + position: 'absolute', + bottom: normalize(20), + right: normalize(15), + width: normalize(108), + height: normalize(25), + borderRadius: 10, + }, + buttonLabel: { + fontWeight: '700', + fontSize: normalize(15), + lineHeight: normalize(17.8), + letterSpacing: normalize(1.3), + textAlign: 'center', + }, + zoomView: {backgroundColor: 'black'}, +}); diff --git a/src/components/comments/index.ts b/src/components/comments/index.ts index ebd93844..77334cb3 100644 --- a/src/components/comments/index.ts +++ b/src/components/comments/index.ts @@ -1,2 +1,3 @@ export {default as CommentTile} from './CommentTile'; export {default as AddComment} from './AddComment'; +export {default as MentionInputControlled} from './MentionInputControlled'; diff --git a/src/components/common/MomentTags.tsx b/src/components/common/MomentTags.tsx index bdd1fbeb..4afacddb 100644 --- a/src/components/common/MomentTags.tsx +++ b/src/components/common/MomentTags.tsx @@ -66,19 +66,21 @@ const MomentTags: React.FC<MomentTagsProps> = ({ useEffect(() => { setTimeout( () => { - imageRef.current.measure( - ( - fx: number, // location of ref relative to parent element - fy: number, - width: number, - height: number, - _x: number, // location of ref relative to entire screen - _y: number, - ) => { - setOffset([fx, fy]); - setImageDimensions([width, height]); - }, - ); + if (imageRef && imageRef.current) { + imageRef.current.measure( + ( + fx: number, // location of ref relative to parent element + fy: number, + width: number, + height: number, + _x: number, // location of ref relative to entire screen + _y: number, + ) => { + setOffset([fx, fy]); + setImageDimensions([width, height]); + }, + ); + } }, editing ? 100 : 0, ); diff --git a/src/components/common/TaggTypeahead.tsx b/src/components/common/TaggTypeahead.tsx index 747e0bb5..7967fdbc 100644 --- a/src/components/common/TaggTypeahead.tsx +++ b/src/components/common/TaggTypeahead.tsx @@ -1,21 +1,29 @@ import React, {Fragment, useEffect, useState} from 'react'; -import {ScrollView, StyleSheet} from 'react-native'; -import {MentionSuggestionsProps} from 'react-native-controlled-mentions'; +import {ScrollView, StyleSheet, View} from 'react-native'; +import {Suggestion} from 'react-native-controlled-mentions'; import {useSelector} from 'react-redux'; import {SEARCH_ENDPOINT_MESSAGES} from '../../constants'; import {loadSearchResults} from '../../services'; import {RootState} from '../../store/rootReducer'; import {ProfilePreviewType} from '../../types'; -import {SCREEN_WIDTH, shuffle} from '../../utils'; +import {SCREEN_HEIGHT, SCREEN_WIDTH, shuffle} from '../../utils'; import TaggUserRowCell from './TaggUserRowCell'; -const TaggTypeahead: React.FC<MentionSuggestionsProps> = ({ +type TaggTypeaheadProps = { + keyword: string | undefined; + component: string | undefined; + onSuggestionPress: (suggestion: Suggestion) => void; +}; + +const TaggTypeahead: React.FC<TaggTypeaheadProps> = ({ keyword, + component, onSuggestionPress, }) => { const {friends} = useSelector((state: RootState) => state.friends); const [results, setResults] = useState<ProfilePreviewType[]>([]); const [height, setHeight] = useState(0); + const margin = component === 'comment' ? -10 : 0; useEffect(() => { if (keyword === '') { @@ -42,40 +50,50 @@ const TaggTypeahead: React.FC<MentionSuggestionsProps> = ({ } return ( - <ScrollView - style={[styles.container, {top: -(height + 30)}]} - showsVerticalScrollIndicator={false} - onLayout={(event) => { - setHeight(event.nativeEvent.layout.height); - }} - keyboardShouldPersistTaps={'always'}> - {results.map((user) => ( - <TaggUserRowCell - onPress={() => { - setResults([]); - onSuggestionPress({ - id: user.id, - name: user.username, - }); - }} - user={user} - /> - ))} - </ScrollView> + <View> + <View style={styles.overlay} /> + <ScrollView + style={[styles.container, {top: -height, margin: margin}]} + showsVerticalScrollIndicator={false} + onLayout={(event) => { + setHeight(event.nativeEvent.layout.height); + }} + keyboardShouldPersistTaps={'always'}> + {results.map((user) => ( + <TaggUserRowCell + onPress={() => { + setResults([]); + onSuggestionPress({ + id: user.id, + name: user.username, + }); + }} + user={user} + /> + ))} + </ScrollView> + </View> ); }; const styles = StyleSheet.create({ container: { - marginLeft: SCREEN_WIDTH * 0.05, - width: SCREEN_WIDTH * 0.9, + width: SCREEN_WIDTH, maxHeight: 264, - borderRadius: 10, backgroundColor: 'white', position: 'absolute', alignSelf: 'center', zIndex: 1, - borderWidth: 1, + }, + overlay: { + width: SCREEN_WIDTH, + height: SCREEN_HEIGHT, + backgroundColor: 'gray', + opacity: 0.4, + position: 'absolute', + alignSelf: 'center', + bottom: 10, + zIndex: -1, }, }); diff --git a/src/components/moments/IndividualMomentTitleBar.tsx b/src/components/moments/IndividualMomentTitleBar.tsx index 4ae9471f..c6bf1423 100644 --- a/src/components/moments/IndividualMomentTitleBar.tsx +++ b/src/components/moments/IndividualMomentTitleBar.tsx @@ -1,45 +1,32 @@ import React from 'react'; -import { - StyleSheet, - Text, - TouchableOpacity, - View, - ViewProps, -} from 'react-native'; -import CloseIcon from '../../assets/ionicons/close-outline.svg'; -import {normalize} from '../../utils'; +import {StyleSheet, Text, View, ViewProps} from 'react-native'; +import {normalize, SCREEN_WIDTH} from '../../utils'; interface IndividualMomentTitleBarProps extends ViewProps { title: string; - close: () => void; } const IndividualMomentTitleBar: React.FC<IndividualMomentTitleBarProps> = ({ title, - close, - style, }) => { return ( - <View style={[styles.container, style]}> - <TouchableOpacity style={styles.closeButton} onPress={close}> - <CloseIcon height={'100%'} width={'100%'} color={'white'} /> - </TouchableOpacity> - <View style={styles.headerContainer}> - <Text style={styles.header}>{title}</Text> + <View style={styles.mainContainer}> + <View style={styles.titleContainer}> + <Text + style={[ + styles.title, + { + fontSize: title.length > 18 ? normalize(14) : normalize(16), + }, + ]}> + {title} + </Text> </View> </View> ); }; const styles = StyleSheet.create({ - container: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'flex-start', - }, - headerContainer: { - width: '80%', - }, - header: { + title: { textAlign: 'center', color: 'white', fontSize: normalize(18), @@ -47,10 +34,19 @@ const styles = StyleSheet.create({ lineHeight: normalize(21.48), letterSpacing: normalize(1.3), }, - closeButton: { - height: '50%', - aspectRatio: 1, - left: '8%', + titleContainer: { + width: '80%', + position: 'absolute', + left: '10%', + right: '10%', + height: normalize(70), + }, + mainContainer: { + flex: 1, + width: SCREEN_WIDTH * 0.6, + flexDirection: 'row', + justifyContent: 'flex-end', + marginVertical: '2%', }, }); diff --git a/src/components/moments/Moment.tsx b/src/components/moments/Moment.tsx index eab4b7e3..47de1c6a 100644 --- a/src/components/moments/Moment.tsx +++ b/src/components/moments/Moment.tsx @@ -88,15 +88,11 @@ const Moment: React.FC<MomentProps> = ({ 'Screenshots', 'UserLibrary', ], - width: 580, - height: 580, - cropping: true, - cropperToolbarTitle: 'Upload a moment', - mediaType: 'photo', + mediaType: 'any', }) .then((picture) => { if (picture.path && picture.filename) { - navigation.navigate('CaptionScreen', { + navigation.navigate('ZoomInCropper', { screenType, title, media: { diff --git a/src/components/moments/MomentCommentPreview.tsx b/src/components/moments/MomentCommentPreview.tsx index e53ed258..232568f1 100644 --- a/src/components/moments/MomentCommentPreview.tsx +++ b/src/components/moments/MomentCommentPreview.tsx @@ -23,7 +23,9 @@ const MomentCommentPreview: React.FC<MomentCommentPreviewProps> = ({ const navigation = useNavigation(); const state = useStore().getState(); const commentCountText = - commentsCount === 0 ? 'No Comments' : commentsCount + ' comments'; + !commentsCount || commentsCount === 0 + ? 'No Comments' + : commentsCount + ' comments'; return ( <TouchableOpacity @@ -35,7 +37,7 @@ const MomentCommentPreview: React.FC<MomentCommentPreviewProps> = ({ }) }> <Text style={styles.whiteBold}>{commentCountText}</Text> - {commentPreview !== null && ( + {commentPreview && ( <View style={styles.previewContainer}> <Image source={{ @@ -50,7 +52,7 @@ const MomentCommentPreview: React.FC<MomentCommentPreviewProps> = ({ {renderTextWithMentions({ value: commentPreview.comment, styles: styles.normalFont, - partTypes: mentionPartTypes('white'), + partTypes: mentionPartTypes('white', 'comment'), onPress: (user: UserType) => navigateToProfile( state, diff --git a/src/components/moments/MomentPost.tsx b/src/components/moments/MomentPost.tsx index d87028e3..6eccf5ab 100644 --- a/src/components/moments/MomentPost.tsx +++ b/src/components/moments/MomentPost.tsx @@ -1,38 +1,77 @@ -import React, {useEffect, useState} from 'react'; -import {StyleSheet} from 'react-native'; -import {useSelector} from 'react-redux'; -import {MomentPostContent, MomentPostHeader} from '.'; +import {useNavigation} from '@react-navigation/native'; +import React, {useContext, useEffect, useRef, useState} from 'react'; +import { + Image, + KeyboardAvoidingView, + Platform, + StatusBar, + StyleSheet, + Text, + TouchableOpacity, + TouchableWithoutFeedback, + View, +} from 'react-native'; +import Animated, {EasingNode} from 'react-native-reanimated'; +import {useDispatch, useSelector, useStore} from 'react-redux'; +import {headerBarOptions} from '../../routes'; +import {MomentContext} from '../../screens/profile/IndividualMoment'; import {deleteMomentTag, loadMomentTags} from '../../services'; +import {loadUserMoments} from '../../store/actions'; import {RootState} from '../../store/rootReducer'; -import {MomentPostType, MomentTagType, ScreenType} from '../../types'; -import {normalize, SCREEN_HEIGHT} from '../../utils'; - +import {MomentPostType, MomentTagType, ScreenType, UserType} from '../../types'; +import { + getTimePosted, + HeaderHeight, + isIPhoneX, + navigateToProfile, + normalize, + SCREEN_HEIGHT, + SCREEN_WIDTH, +} from '../../utils'; +import {mentionPartTypes, renderTextWithMentions} from '../../utils/comments'; +import {AddComment} from '../comments'; +import CommentsCount from '../comments/CommentsCount'; +import {MomentTags} from '../common'; +import {MomentMoreInfoDrawer, TaggAvatar} from '../profile'; +import IndividualMomentTitleBar from './IndividualMomentTitleBar'; interface MomentPostProps { moment: MomentPostType; userXId: string | undefined; screenType: ScreenType; - index: number; } const MomentPost: React.FC<MomentPostProps> = ({ moment, userXId, screenType, - index, }) => { + const navigation = useNavigation(); + const dispatch = useDispatch(); const {userId: loggedInUserId, username: loggedInUsername} = useSelector( (state: RootState) => state.user.user, ); - - const { - user: {username}, - } = useSelector((state: RootState) => + const {user} = useSelector((state: RootState) => userXId ? state.userX[screenType][userXId] : state.user, ); + const state: RootState = useStore().getState(); + const isOwnProfile = user.username === loggedInUsername; + const [tags, setTags] = useState<MomentTagType[]>([]); + const [visible, setVisible] = useState(false); + const [drawerVisible, setDrawerVisible] = useState(false); + const [hideText, setHideText] = useState(false); + + const [fadeValue, setFadeValue] = useState<Animated.Value<number>>( + new Animated.Value(0), + ); + const [commentCount, setCommentCount] = useState<number>( + moment.comments_count, + ); + const [aspectRatio, setAspectRatio] = useState<number>(1); const [momentTagId, setMomentTagId] = useState<string>(''); - const isOwnProfile = username === loggedInUsername; + const imageRef = useRef(null); + const {keyboardVisible} = useContext(MomentContext); /* * Load tags on initial render to pass tags data to moment header and content @@ -41,7 +80,7 @@ const MomentPost: React.FC<MomentPostProps> = ({ loadMomentTags(moment.moment_id).then((response) => { setTags(response ? response : []); }); - }, []); + }, [moment]); /* * Check if loggedInUser has been tagged in the picture and set the id @@ -71,34 +110,270 @@ const MomentPost: React.FC<MomentPostProps> = ({ } }; + useEffect( + () => + navigation.setOptions({ + ...headerBarOptions('white', ''), + headerTitle: () => ( + <IndividualMomentTitleBar title={moment.moment_category} /> + ), + }), + [moment.moment_id], + ); + + /* + * Determines if an image is 9:16 to set aspect ratio of current image and + * determine if image must be displayed in full screen or not + */ + useEffect(() => { + Image.getSize( + moment.moment_url, + (w, h) => { + setAspectRatio(w / h); + }, + (err) => console.log(err), + ); + }, []); + + /* + * To animate tags display + */ + useEffect(() => { + const fade = async () => { + Animated.timing(fadeValue, { + toValue: 1, + duration: 250, + easing: EasingNode.linear, + }).start(); + }; + fade(); + }, [fadeValue]); + + useEffect(() => { + if (!keyboardVisible && hideText) { + setHideText(false); + } + }, [keyboardVisible, hideText]); + + const MomentPosterPreview = () => ( + <View style={styles.momentPosterContainer}> + <TouchableOpacity + onPress={() => + navigateToProfile(state, dispatch, navigation, screenType, user) + } + style={styles.header}> + <TaggAvatar + style={styles.avatar} + userXId={userXId} + screenType={screenType} + editable={false} + /> + <Text style={styles.headerText}>{user.username}</Text> + </TouchableOpacity> + </View> + ); + return ( <> - <MomentPostHeader - style={styles.postHeader} - userXId={userXId} - screenType={screenType} - username={isOwnProfile ? loggedInUsername : username} - momentTagId={momentTagId} - removeTag={removeTag} - moment={moment} - tags={tags} - /> - <MomentPostContent - style={styles.postContent} - moment={moment} - screenType={screenType} - momentTags={tags} - index={index} - /> + <StatusBar barStyle={'light-content'} /> + <View style={styles.mainContainer}> + <View style={styles.imageContainer}> + <Image + source={{uri: moment.moment_url}} + style={[ + styles.image, + { + height: SCREEN_WIDTH / aspectRatio, + }, + ]} + resizeMode={'contain'} + ref={imageRef} + /> + </View> + {visible && ( + <Animated.View style={[styles.tagsContainer, {opacity: fadeValue}]}> + <MomentTags + editing={false} + tags={tags} + setTags={() => null} + imageRef={imageRef} + /> + </Animated.View> + )} + <TouchableWithoutFeedback + onPress={() => { + setVisible(!visible); + setFadeValue(new Animated.Value(0)); + }}> + <View style={styles.contentContainer}> + <View style={styles.topContainer}> + <MomentMoreInfoDrawer + isOpen={drawerVisible} + setIsOpen={setDrawerVisible} + isOwnProfile={isOwnProfile} + momentTagId={momentTagId} + removeTag={removeTag} + dismissScreenAndUpdate={() => { + dispatch(loadUserMoments(loggedInUserId)); + navigation.goBack(); + }} + screenType={screenType} + moment={moment} + tags={tags} + /> + </View> + <KeyboardAvoidingView + behavior={Platform.OS === 'ios' ? 'padding' : 'height'} + keyboardVerticalOffset={-20}> + <View style={styles.bottomContainer}> + {tags.length > 0 && ( + <Image + source={require('../../assets/icons/tag_indicate.png')} + style={styles.tagIcon} + /> + )} + <View style={styles.commentsCountContainer}> + <CommentsCount moment={moment} screenType={screenType} /> + </View> + <MomentPosterPreview /> + {!hideText && ( + <> + {moment.caption !== '' && + renderTextWithMentions({ + value: moment.caption, + styles: styles.captionText, + partTypes: mentionPartTypes('white', 'caption'), + onPress: (userLocal: UserType) => + navigateToProfile( + state, + dispatch, + navigation, + screenType, + userLocal, + ), + })} + </> + )} + <View> + <AddComment + placeholderText={'Add a comment here!'} + momentId={moment.moment_id} + callback={() => { + setCommentCount(commentCount + 1); + }} + onFocus={() => { + setHideText(true); + }} + isKeyboardAvoiding={false} + theme={'dark'} + /> + <Text style={styles.text}> + {getTimePosted(moment.date_created)} + </Text> + </View> + </View> + </KeyboardAvoidingView> + </View> + </TouchableWithoutFeedback> + </View> </> ); }; const styles = StyleSheet.create({ - postHeader: {}, - postContent: { - minHeight: SCREEN_HEIGHT * 0.8, - paddingBottom: normalize(20), + image: { + zIndex: 0, + }, + imageContainer: { + height: SCREEN_HEIGHT, + width: SCREEN_WIDTH, + flexDirection: 'column', + justifyContent: 'center', + overflow: 'hidden', + }, + text: { + marginHorizontal: '5%', + color: 'white', + fontWeight: '500', + textAlign: 'right', + marginTop: 5, + }, + captionText: { + position: 'relative', + marginHorizontal: '5%', + color: '#ffffff', + fontWeight: '500', + fontSize: normalize(13), + lineHeight: normalize(15.51), + letterSpacing: normalize(0.6), + marginBottom: normalize(5), + width: SCREEN_WIDTH * 0.79, + }, + tagIcon: { + width: normalize(30), + height: normalize(30), + bottom: normalize(20), + left: '5%', + }, + avatar: { + width: 48, + aspectRatio: 1, + borderRadius: 100, + marginLeft: '3%', + }, + headerText: { + fontSize: 15, + fontWeight: 'bold', + color: 'white', + paddingHorizontal: '3%', + }, + header: { + alignItems: 'center', + flexDirection: 'row', + marginBottom: normalize(15), + alignSelf: 'flex-start', + }, + momentPosterContainer: { + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + }, + commentsCountContainer: { + position: 'absolute', + right: '2%', + bottom: SCREEN_HEIGHT * 0.12, + }, + bottomContainer: { + flexDirection: 'column', + justifyContent: 'flex-end', + }, + topContainer: { + paddingTop: isIPhoneX() ? HeaderHeight : '6%', + alignSelf: 'flex-end', + paddingRight: '8%', + }, + contentContainer: { + position: 'absolute', + width: SCREEN_WIDTH, + height: SCREEN_HEIGHT, + flexDirection: 'column', + justifyContent: 'space-between', + paddingBottom: '24%', + }, + tagsContainer: { + position: 'absolute', + top: 0, + bottom: 0, + left: 0, + right: 0, + marginBottom: '3%', + }, + mainContainer: { + backgroundColor: 'black', + width: SCREEN_WIDTH, + height: SCREEN_HEIGHT, + flexDirection: 'column', + justifyContent: 'center', }, }); diff --git a/src/components/moments/MomentPostHeader.tsx b/src/components/moments/MomentPostHeader.tsx deleted file mode 100644 index 5f26951a..00000000 --- a/src/components/moments/MomentPostHeader.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import {useNavigation} from '@react-navigation/native'; -import React, {useState} from 'react'; -import { - StyleSheet, - Text, - TouchableOpacity, - View, - ViewProps, -} from 'react-native'; -import {useDispatch, useSelector, useStore} from 'react-redux'; -import {loadUserMoments} from '../../store/actions'; -import {RootState} from '../../store/rootReducer'; -import {MomentTagType, MomentType, ScreenType} from '../../types'; -import {fetchUserX, userXInStore} from '../../utils'; -import {MomentMoreInfoDrawer} from '../profile'; -import TaggAvatar from '../profile/TaggAvatar'; - -interface MomentPostHeaderProps extends ViewProps { - userXId?: string; - screenType: ScreenType; - username: string; - momentTagId: string; - removeTag: () => Promise<void>; - moment: MomentType; - tags: MomentTagType[]; -} - -const MomentPostHeader: React.FC<MomentPostHeaderProps> = ({ - userXId, - screenType, - username, - style, - momentTagId, - removeTag, - moment, - tags, -}) => { - const [drawerVisible, setDrawerVisible] = useState(false); - const dispatch = useDispatch(); - const navigation = useNavigation(); - const {userId: loggedInUserId, username: loggedInUserName} = useSelector( - (state: RootState) => state.user.user, - ); - const state: RootState = useStore().getState(); - const isOwnProfile = loggedInUserName === username; - const navigateToProfile = async () => { - if (userXId && !userXInStore(state, screenType, userXId)) { - await fetchUserX( - dispatch, - {userId: userXId, username: username}, - screenType, - ); - } - navigation.navigate('Profile', { - userXId: isOwnProfile ? undefined : userXId, - screenType, - }); - }; - - return ( - <View style={[styles.container, style]}> - <TouchableOpacity onPress={navigateToProfile} style={styles.header}> - <TaggAvatar - style={styles.avatar} - userXId={userXId} - screenType={screenType} - editable={false} - /> - <Text style={styles.headerText}>{username}</Text> - </TouchableOpacity> - <MomentMoreInfoDrawer - isOpen={drawerVisible} - setIsOpen={setDrawerVisible} - isOwnProfile={isOwnProfile} - momentTagId={momentTagId} - removeTag={removeTag} - dismissScreenAndUpdate={() => { - dispatch(loadUserMoments(loggedInUserId)); - navigation.goBack(); - }} - screenType={screenType} - moment={moment} - tags={tags} - /> - </View> - ); -}; - -const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'space-around', - flexDirection: 'row', - alignItems: 'center', - marginVertical: '2%', - }, - header: { - alignItems: 'center', - flexDirection: 'row', - flex: 1, - }, - avatar: { - flex: 0.2, - aspectRatio: 1, - borderRadius: 999999, - marginLeft: '3%', - }, - headerText: { - fontSize: 15, - fontWeight: 'bold', - color: 'white', - paddingHorizontal: '3%', - flex: 1, - }, -}); -export default MomentPostHeader; diff --git a/src/components/moments/index.ts b/src/components/moments/index.ts index c1419cfd..cac2da2e 100644 --- a/src/components/moments/index.ts +++ b/src/components/moments/index.ts @@ -1,7 +1,5 @@ export {default as IndividualMomentTitleBar} from './IndividualMomentTitleBar'; export {default as CaptionScreenHeader} from './CaptionScreenHeader'; -export {default as MomentPostHeader} from './MomentPostHeader'; -export {default as MomentPostContent} from './MomentPostContent'; export {default as Moment} from './Moment'; export {default as TagFriendsFooter} from './TagFriendsFoooter'; export {default as MomentPost} from './MomentPost'; diff --git a/src/components/moments/MomentPostContent.tsx b/src/components/moments/legacy/MomentPostContent.tsx index 4d2554d8..d372a332 100644 --- a/src/components/moments/MomentPostContent.tsx +++ b/src/components/moments/legacy/MomentPostContent.tsx @@ -5,26 +5,29 @@ import {TouchableWithoutFeedback} from 'react-native-gesture-handler'; import Animated, {EasingNode} from 'react-native-reanimated'; import Video from 'react-native-video'; import {useDispatch, useStore} from 'react-redux'; -import {MomentContext} from '../../screens/profile/IndividualMoment'; -import {RootState} from '../../store/rootReducer'; +import {MomentContext} from '../../../screens/profile/IndividualMoment'; +import {RootState} from '../../../store/rootReducer'; import { MomentCommentPreviewType, MomentPostType, MomentTagType, ScreenType, UserType, -} from '../../types'; +} from '../../../types'; import { getLoggedInUserAsProfilePreview, getTimePosted, navigateToProfile, normalize, SCREEN_WIDTH, -} from '../../utils'; -import {mentionPartTypes, renderTextWithMentions} from '../../utils/comments'; -import {AddComment} from '../comments'; -import {MomentTags} from '../common'; -import MomentCommentPreview from './MomentCommentPreview'; +} from '../../../utils'; +import { + mentionPartTypes, + renderTextWithMentions, +} from '../../../utils/comments'; +import {AddComment} from '../../comments'; +import {MomentTags} from '../../common'; +import MomentCommentPreview from '../MomentCommentPreview'; interface MomentPostContentProps extends ViewProps { screenType: ScreenType; diff --git a/src/components/profile/MomentMoreInfoDrawer.tsx b/src/components/profile/MomentMoreInfoDrawer.tsx index c70df875..910aa095 100644 --- a/src/components/profile/MomentMoreInfoDrawer.tsx +++ b/src/components/profile/MomentMoreInfoDrawer.tsx @@ -3,7 +3,6 @@ import React, {useEffect, useState} from 'react'; import { Alert, GestureResponderEvent, - StyleSheet, TextStyle, TouchableOpacity, ViewProps, @@ -174,7 +173,6 @@ const MomentMoreInfoDrawer: React.FC<MomentMoreInfoDrawerProps> = (props) => { return ( <> <TouchableOpacity - style={styles.icon} onPress={() => { setIsOpen(true); }}> @@ -189,10 +187,4 @@ const MomentMoreInfoDrawer: React.FC<MomentMoreInfoDrawerProps> = (props) => { ); }; -const styles = StyleSheet.create({ - icon: { - marginRight: '3%', - }, -}); - export default MomentMoreInfoDrawer; diff --git a/src/components/profile/PublicProfile.tsx b/src/components/profile/PublicProfile.tsx index 8a80c56f..6b991d7c 100644 --- a/src/components/profile/PublicProfile.tsx +++ b/src/components/profile/PublicProfile.tsx @@ -87,7 +87,9 @@ const PublicProfile: React.FC<ContentProps> = ({ scrollViewRef.current ) { setScrollEnabled(false); - scrollViewRef.current.scrollTo({y: 0}); + if (scrollViewRef && scrollViewRef.current) { + scrollViewRef.current.scrollTo({y: 0}); + } navigation.navigate('MomentUploadPrompt', { screenType, momentCategory: momentCategories[0], diff --git a/src/routes/main/MainStackNavigator.tsx b/src/routes/main/MainStackNavigator.tsx index 522a18dd..c518d75e 100644 --- a/src/routes/main/MainStackNavigator.tsx +++ b/src/routes/main/MainStackNavigator.tsx @@ -110,6 +110,11 @@ export type MainStackParams = { ChatList: undefined; Chat: undefined; NewChatModal: undefined; + ZoomInCropper: { + media: {filename: string; uri: string; isVideo: boolean}; + screenType: ScreenType; + title: string; + }; }; export const MainStack = createStackNavigator<MainStackParams>(); diff --git a/src/routes/main/MainStackScreen.tsx b/src/routes/main/MainStackScreen.tsx index 3be2ff28..9e3747f9 100644 --- a/src/routes/main/MainStackScreen.tsx +++ b/src/routes/main/MainStackScreen.tsx @@ -39,6 +39,7 @@ import MutualBadgeHolders from '../../screens/suggestedPeople/MutualBadgeHolders import {ScreenType} from '../../types'; import {AvatarHeaderHeight, ChatHeaderHeight, SCREEN_WIDTH} from '../../utils'; import {MainStack, MainStackParams} from './MainStackNavigator'; +import {ZoomInCropper} from '../../components/comments/ZoomInCropper'; /** * Profile : To display the logged in user's profile when the userXId passed in to it is (undefined | null | empty string) else displays profile of the user being visited. @@ -209,6 +210,7 @@ const MainStackScreen: React.FC<MainStackProps> = ({route}) => { options={{ ...modalStyle, gestureEnabled: false, + ...headerBarOptions('white', ''), }} /> <MainStack.Screen @@ -325,6 +327,13 @@ const MainStackScreen: React.FC<MainStackProps> = ({route}) => { gestureEnabled: false, }} /> + <MainStack.Screen + name="ZoomInCropper" + component={ZoomInCropper} + options={{ + gestureEnabled: false, + }} + /> </MainStack.Navigator> ); }; diff --git a/src/screens/moments/TagFriendsScreen.tsx b/src/screens/moments/TagFriendsScreen.tsx index bda38651..db7456bc 100644 --- a/src/screens/moments/TagFriendsScreen.tsx +++ b/src/screens/moments/TagFriendsScreen.tsx @@ -27,6 +27,8 @@ const TagFriendsScreen: React.FC<TagFriendsScreenProps> = ({route}) => { const navigation = useNavigation(); const imageRef = useRef(null); const [tags, setTags] = useState<MomentTagType[]>([]); + const [imageWidth, setImageWidth] = useState<number>(0); + const [imageHeight, setImageHeight] = useState<number>(0); /* * Update list of tagged users from route params @@ -45,6 +47,32 @@ const TagFriendsScreen: React.FC<TagFriendsScreenProps> = ({route}) => { }); }; + /* + * Calculating image width and height with respect to it's enclosing view's dimensions + */ + useEffect(() => { + if (imageRef && imageRef.current) { + Image.getSize( + media.uri, + (w, h) => { + const imageAspectRatio = w / h; + + // aspectRatio: >= 1 [Landscape] [1:1] + if (imageAspectRatio >= 1) { + setImageWidth(SCREEN_WIDTH); + setImageHeight(SCREEN_WIDTH / imageAspectRatio); + } + // aspectRatio: < 1 [Portrait] + else if (imageAspectRatio < 1) { + setImageHeight(SCREEN_WIDTH); + setImageWidth(SCREEN_WIDTH * imageAspectRatio); + } + }, + (err) => console.log(err), + ); + } + }, []); + return ( <SearchBackground> <View style={styles.contentContainer}> @@ -80,11 +108,24 @@ const TagFriendsScreen: React.FC<TagFriendsScreenProps> = ({route}) => { /> </View> ) : ( + // <Image + // ref={imageRef} + // style={styles.media} + // source={{uri: media.uri}} + // resizeMode={'cover'} + // /> <Image ref={imageRef} - style={styles.media} + style={[ + { + width: imageWidth, + height: imageHeight, + marginVertical: (SCREEN_WIDTH - imageHeight) / 2, + marginHorizontal: (SCREEN_WIDTH - imageWidth) / 2, + }, + styles.media, + ]} source={{uri: media.uri}} - resizeMode={'cover'} /> )} </TouchableWithoutFeedback> @@ -127,11 +168,16 @@ const styles = StyleSheet.create({ header: { marginVertical: 20, }, + // media: { + // position: 'relative', + // width: SCREEN_WIDTH, + // aspectRatio: 1, + // marginBottom: '3%', + // }, media: { - position: 'relative', - width: SCREEN_WIDTH, - aspectRatio: 1, - marginBottom: '3%', + zIndex: 0, + justifyContent: 'center', + alignSelf: 'center', }, text: { position: 'relative', diff --git a/src/screens/profile/CaptionScreen.tsx b/src/screens/profile/CaptionScreen.tsx index d53570cb..364b81a3 100644 --- a/src/screens/profile/CaptionScreen.tsx +++ b/src/screens/profile/CaptionScreen.tsx @@ -13,7 +13,7 @@ import { TouchableWithoutFeedback, View, } from 'react-native'; -import {MentionInput} from 'react-native-controlled-mentions'; +import {MentionInputControlled} from '../../components'; import {Button, normalize} from 'react-native-elements'; import Video from 'react-native-video'; import {useDispatch, useSelector} from 'react-redux'; @@ -237,16 +237,16 @@ const CaptionScreen: React.FC<CaptionScreenProps> = ({route, navigation}) => { <Image style={styles.media} source={{uri: mediaUri}} - resizeMode={'cover'} + resizeMode={'contain'} /> )} - <MentionInput + <MentionInputControlled containerStyle={styles.text} placeholder="Write something....." placeholderTextColor="gray" value={caption} onChange={setCaption} - partTypes={mentionPartTypes('blue')} + partTypes={mentionPartTypes('blue', 'caption')} /> <TouchableOpacity onPress={() => diff --git a/src/screens/profile/IndividualMoment.tsx b/src/screens/profile/IndividualMoment.tsx index f8113aba..ca31ad5b 100644 --- a/src/screens/profile/IndividualMoment.tsx +++ b/src/screens/profile/IndividualMoment.tsx @@ -1,20 +1,14 @@ -import {BlurView} from '@react-native-community/blur'; import {RouteProp} from '@react-navigation/native'; import {StackNavigationProp} from '@react-navigation/stack'; import React, {useEffect, useRef, useState} from 'react'; -import {FlatList, Keyboard, StyleSheet} from 'react-native'; +import {FlatList, Keyboard} from 'react-native'; import {useSelector} from 'react-redux'; -import {IndividualMomentTitleBar, MomentPost} from '../../components'; +import {MomentPost, TabsGradient} from '../../components'; import {AVATAR_DIM} from '../../constants'; import {MainStackParams} from '../../routes'; import {RootState} from '../../store/rootreducer'; import {MomentPostType} from '../../types'; -import { - isIPhoneX, - normalize, - SCREEN_HEIGHT, - StatusBarHeight, -} from '../../utils'; +import {isIPhoneX} from '../../utils'; /** * Individual moment view opened when user clicks on a moment tile @@ -39,10 +33,7 @@ interface IndividualMomentProps { navigation: IndividualMomentNavigationProp; } -const IndividualMoment: React.FC<IndividualMomentProps> = ({ - route, - navigation, -}) => { +const IndividualMoment: React.FC<IndividualMomentProps> = ({route}) => { const { userXId, screenType, @@ -84,55 +75,32 @@ const IndividualMoment: React.FC<IndividualMomentProps> = ({ 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} - contentContainerStyle={styles.listContentContainer} - renderItem={({item, index}) => ( - <MomentPost - moment={item} - userXId={userXId} - screenType={screenType} - index={index} - /> - )} - keyExtractor={(item, _) => item.moment_id} - showsVerticalScrollIndicator={false} - initialScrollIndex={initialIndex} - 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}); - // }); - }} - /> - </BlurView> + <FlatList + ref={scrollRef} + data={momentData} + renderItem={({item}) => ( + <MomentPost + key={item.moment_id} + moment={item} + userXId={userXId} + screenType={screenType} + /> + )} + keyExtractor={(item, _) => item.moment_id} + showsVerticalScrollIndicator={false} + initialScrollIndex={initialIndex} + onScrollToIndexFailed={(info) => { + setTimeout(() => { + scrollRef.current?.scrollToIndex({ + index: info.index, + }); + }, 500); + }} + pagingEnabled + /> + <TabsGradient /> </MomentContext.Provider> ); }; -const styles = StyleSheet.create({ - contentContainer: { - paddingTop: StatusBarHeight, - flex: 1, - }, - header: { - height: normalize(70), - }, - listContentContainer: { - paddingBottom: SCREEN_HEIGHT * 0.2, - }, -}); export default IndividualMoment; diff --git a/src/screens/profile/MomentCommentsScreen.tsx b/src/screens/profile/MomentCommentsScreen.tsx index 7dfe8ae9..402e5f44 100644 --- a/src/screens/profile/MomentCommentsScreen.tsx +++ b/src/screens/profile/MomentCommentsScreen.tsx @@ -48,9 +48,8 @@ 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/utils/comments.tsx b/src/utils/comments.tsx index 910b44e7..28879622 100644 --- a/src/utils/comments.tsx +++ b/src/utils/comments.tsx @@ -79,13 +79,16 @@ export const renderTextWithMentions: React.FC<RenderProps> = ({ ); }; -export const mentionPartTypes: (theme: 'blue' | 'white') => PartType[] = ( - theme, -) => { +export const mentionPartTypes: ( + theme: 'blue' | 'white', + component: 'caption' | 'comment', +) => PartType[] = (theme, component) => { return [ { trigger: '@', - renderSuggestions: (props) => <TaggTypeahead {...props} />, + renderSuggestions: (props) => ( + <TaggTypeahead component={component} {...props} /> + ), allowedSpacesCount: 0, isInsertSpaceAfterMention: true, textStyle: _textStyle(theme), |