diff options
Diffstat (limited to 'src/components/comments')
-rw-r--r-- | src/components/comments/AddComment.tsx | 44 | ||||
-rw-r--r-- | src/components/comments/CommentTextField.tsx | 164 | ||||
-rw-r--r-- | src/components/comments/CommentsCount.tsx | 48 | ||||
-rw-r--r-- | src/components/comments/MentionInputControlled.tsx | 70 | ||||
-rw-r--r-- | src/components/comments/ZoomInCropper.tsx | 201 | ||||
-rw-r--r-- | src/components/comments/index.ts | 1 |
6 files changed, 492 insertions, 36 deletions
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'; |