diff options
Diffstat (limited to 'src/screens')
| -rw-r--r-- | src/screens/main/NotificationsScreen.tsx | 6 | ||||
| -rw-r--r-- | src/screens/moments/TagFriendsScreen.tsx | 4 | ||||
| -rw-r--r-- | src/screens/onboarding/BasicInfoOnboarding.tsx | 6 | ||||
| -rw-r--r-- | src/screens/profile/CaptionScreen.tsx | 97 | ||||
| -rw-r--r-- | src/screens/profile/EditProfile.tsx | 15 | ||||
| -rw-r--r-- | src/screens/profile/IndividualMoment.tsx | 135 | ||||
| -rw-r--r-- | src/screens/profile/InviteFriendsScreen.tsx | 56 | ||||
| -rw-r--r-- | src/screens/profile/MomentCommentsScreen.tsx | 5 |
8 files changed, 201 insertions, 123 deletions
diff --git a/src/screens/main/NotificationsScreen.tsx b/src/screens/main/NotificationsScreen.tsx index ebcccc8e..03842b0a 100644 --- a/src/screens/main/NotificationsScreen.tsx +++ b/src/screens/main/NotificationsScreen.tsx @@ -19,6 +19,7 @@ import {SafeAreaView} from 'react-native-safe-area-context'; import {useDispatch, useSelector} from 'react-redux'; import FindFriendsBlueIcon from '../../assets/icons/findFriends/find-friends-blue-icon.svg'; import {TabsGradient} from '../../components'; +import EmptyContentView from '../../components/common/EmptyContentView'; import {Notification} from '../../components/notifications'; import {NewChatPrompt} from '../../components/notifications/NotificationPrompts'; import { @@ -28,7 +29,6 @@ import { import {RootState} from '../../store/rootReducer'; import {NotificationType, ScreenType} from '../../types'; import {getDateAge, normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; -import EmptyContentView from '../../components/common/EmptyContentView'; const NotificationsScreen: React.FC = () => { const {newNotificationReceived} = useSelector( @@ -290,7 +290,9 @@ const NotificationsScreen: React.FC = () => { contentContainerStyle={styles.container} stickySectionHeadersEnabled={false} sections={sectionedNotifications} - keyExtractor={(_item, index) => index.toString()} + keyExtractor={(item, index) => + item.timestamp.toString() + index.toString() + } renderItem={renderNotification} renderSectionHeader={renderSectionHeader} renderSectionFooter={renderSectionFooter} diff --git a/src/screens/moments/TagFriendsScreen.tsx b/src/screens/moments/TagFriendsScreen.tsx index c8bca9f4..570c3776 100644 --- a/src/screens/moments/TagFriendsScreen.tsx +++ b/src/screens/moments/TagFriendsScreen.tsx @@ -30,7 +30,7 @@ interface TagFriendsScreenProps { route: TagFriendsScreenRouteProps; } const TagFriendsScreen: React.FC<TagFriendsScreenProps> = ({route}) => { - const {image, selectedTags} = route.params; + const {imagePath, selectedTags} = route.params; const navigation = useNavigation(); const imageRef = useRef(null); const [tags, setTags] = useState<MomentTagType[]>([]); @@ -85,7 +85,7 @@ const TagFriendsScreen: React.FC<TagFriendsScreenProps> = ({route}) => { <Image ref={imageRef} style={styles.image} - source={{uri: image.path}} + source={{uri: imagePath}} resizeMode={'cover'} /> </TouchableWithoutFeedback> diff --git a/src/screens/onboarding/BasicInfoOnboarding.tsx b/src/screens/onboarding/BasicInfoOnboarding.tsx index e5e6f59b..d5998ac1 100644 --- a/src/screens/onboarding/BasicInfoOnboarding.tsx +++ b/src/screens/onboarding/BasicInfoOnboarding.tsx @@ -13,7 +13,7 @@ import { TouchableOpacity, } from 'react-native'; import {normalize} from 'react-native-elements'; -import Animated, {Easing, useValue} from 'react-native-reanimated'; +import Animated, {EasingNode, useValue} from 'react-native-reanimated'; import { ArrowButton, Background, @@ -99,7 +99,7 @@ const BasicInfoOnboarding: React.FC<BasicInfoOnboardingProps> = ({route}) => { Animated.timing(fadeButtonValue, { toValue: target, duration: 100, - easing: Easing.linear, + easing: EasingNode.linear, }).start(); }; @@ -108,7 +108,7 @@ const BasicInfoOnboarding: React.FC<BasicInfoOnboardingProps> = ({route}) => { Animated.timing(fadeValue, { toValue: 1, duration: 1000, - easing: Easing.linear, + easing: EasingNode.linear, }).start(); }; fade(); diff --git a/src/screens/profile/CaptionScreen.tsx b/src/screens/profile/CaptionScreen.tsx index 755c668b..253346d5 100644 --- a/src/screens/profile/CaptionScreen.tsx +++ b/src/screens/profile/CaptionScreen.tsx @@ -21,9 +21,13 @@ import {SearchBackground} from '../../components'; import {CaptionScreenHeader} from '../../components/'; import TaggLoadingIndicator from '../../components/common/TaggLoadingIndicator'; import {TAGG_LIGHT_BLUE_2} from '../../constants'; -import {ERROR_UPLOAD, SUCCESS_PIC_UPLOAD} from '../../constants/strings'; +import { + ERROR_SOMETHING_WENT_WRONG_REFRESH, + ERROR_UPLOAD, + SUCCESS_PIC_UPLOAD, +} from '../../constants/strings'; import {MainStackParams} from '../../routes'; -import {postMoment, postMomentTags} from '../../services'; +import {patchMoment, postMoment, postMomentTags} from '../../services'; import { loadUserMoments, updateProfileCompletionStage, @@ -47,14 +51,16 @@ interface CaptionScreenProps { } const CaptionScreen: React.FC<CaptionScreenProps> = ({route, navigation}) => { - const {title, image, screenType, selectedTags} = route.params; + const {title, image, screenType, selectedTags, moment} = route.params; const { user: {userId}, } = useSelector((state: RootState) => state.user); const dispatch = useDispatch(); - const [caption, setCaption] = useState(''); + const [caption, setCaption] = useState(moment ? moment.caption : ''); const [loading, setLoading] = useState(false); - const [tags, setTags] = useState<MomentTagType[]>([]); + const [tags, setTags] = useState<MomentTagType[]>( + selectedTags ? selectedTags : [], + ); const [taggedList, setTaggedList] = useState<string>(''); useEffect(() => { @@ -84,22 +90,37 @@ const CaptionScreen: React.FC<CaptionScreenProps> = ({route, navigation}) => { }); }; - const handleShare = async () => { - const handleFailed = () => { - setLoading(false); - setTimeout(() => { - Alert.alert(ERROR_UPLOAD); - }, 500); - }; - const handleSuccess = () => { + const handleFailed = () => { + setLoading(false); + setTimeout(() => { + Alert.alert(moment ? ERROR_SOMETHING_WENT_WRONG_REFRESH : ERROR_UPLOAD); + }, 500); + }; + const handleSuccess = () => { + setLoading(false); + if (moment) { setLoading(false); + navigation.goBack(); + } else { navigateToProfile(); setTimeout(() => { Alert.alert(SUCCESS_PIC_UPLOAD); }, 500); - }; + } + }; + + const formattedTags = () => { + return tags.map((tag) => ({ + x: Math.floor(tag.x), + y: Math.floor(tag.y), + z: Math.floor(tag.z), + user_id: tag.user.id, + })); + }; + + const handleShare = async () => { setLoading(true); - if (!image.filename) { + if (!image?.filename || !title) { return; } const momentResponse = await postMoment( @@ -115,12 +136,7 @@ const CaptionScreen: React.FC<CaptionScreenProps> = ({route, navigation}) => { } const momentTagResponse = await postMomentTags( momentResponse.moment_id, - tags.map((tag) => ({ - x: Math.floor(tag.x), - y: Math.floor(tag.y), - z: Math.floor(tag.z), - user_id: tag.user.id, - })), + formattedTags(), ); if (!momentTagResponse) { handleFailed(); @@ -133,6 +149,23 @@ const CaptionScreen: React.FC<CaptionScreenProps> = ({route, navigation}) => { handleSuccess(); }; + const handleDone = async () => { + setLoading(true); + if (moment?.moment_id) { + const success = await patchMoment( + moment.moment_id, + caption, + formattedTags(), + ); + if (success) { + dispatch(loadUserMoments(userId)); + handleSuccess(); + } else { + handleFailed(); + } + } + }; + return ( <SearchBackground> {loading ? <TaggLoadingIndicator fullscreen /> : <Fragment />} @@ -145,20 +178,25 @@ const CaptionScreen: React.FC<CaptionScreenProps> = ({route, navigation}) => { <Button title="Cancel" buttonStyle={styles.button} - onPress={() => navigateToProfile()} + onPress={() => + moment ? navigation.goBack() : navigateToProfile() + } /> <Button - title="Share" + title={moment ? 'Done' : 'Share'} titleStyle={styles.shareButtonTitle} buttonStyle={styles.button} - onPress={handleShare} + onPress={moment ? handleDone : handleShare} /> </View> - <CaptionScreenHeader style={styles.header} {...{title: title}} /> + <CaptionScreenHeader + style={styles.header} + {...{title: moment ? moment.moment_category : title}} + /> {/* this is the image we want to center our tags' initial location within */} <Image style={styles.image} - source={{uri: image.path}} + source={{uri: moment ? moment.moment_url : image?.path}} resizeMode={'cover'} /> <MentionInputControlled @@ -172,8 +210,11 @@ const CaptionScreen: React.FC<CaptionScreenProps> = ({route, navigation}) => { <TouchableOpacity onPress={() => navigation.navigate('TagFriendsScreen', { - image: image, - screenType: screenType, + imagePath: moment + ? moment.moment_url + : image + ? image.path + : '', selectedTags: tags, }) } diff --git a/src/screens/profile/EditProfile.tsx b/src/screens/profile/EditProfile.tsx index 26802e45..20a62b19 100644 --- a/src/screens/profile/EditProfile.tsx +++ b/src/screens/profile/EditProfile.tsx @@ -305,14 +305,13 @@ const EditProfile: React.FC<EditProfileProps> = ({route, navigation}) => { type: 'image/jpg', }); } - if (form.website) { - if (form.isValidWebsite) { - request.append('website', form.website); - } else { - setForm({...form, attemptedSubmit: false}); - setTimeout(() => setForm({...form, attemptedSubmit: true})); - invalidFields = true; - } + + if (form.isValidWebsite) { + request.append('website', form.website); + } else { + setForm({...form, attemptedSubmit: false}); + setTimeout(() => setForm({...form, attemptedSubmit: true})); + invalidFields = true; } if (form.bio) { diff --git a/src/screens/profile/IndividualMoment.tsx b/src/screens/profile/IndividualMoment.tsx index 4ad4515d..f8113aba 100644 --- a/src/screens/profile/IndividualMoment.tsx +++ b/src/screens/profile/IndividualMoment.tsx @@ -1,103 +1,138 @@ import {BlurView} from '@react-native-community/blur'; import {RouteProp} from '@react-navigation/native'; import {StackNavigationProp} from '@react-navigation/stack'; -import React from 'react'; -import {FlatList, StyleSheet, View} from 'react-native'; +import React, {useEffect, useRef, useState} from 'react'; +import {FlatList, Keyboard, StyleSheet} from 'react-native'; import {useSelector} from 'react-redux'; import {IndividualMomentTitleBar, MomentPost} from '../../components'; +import {AVATAR_DIM} from '../../constants'; import {MainStackParams} from '../../routes'; import {RootState} from '../../store/rootreducer'; -import {MomentType} from '../../types'; -import {SCREEN_HEIGHT, SCREEN_WIDTH, StatusBarHeight} from '../../utils'; +import {MomentPostType} from '../../types'; +import { + isIPhoneX, + normalize, + SCREEN_HEIGHT, + StatusBarHeight, +} from '../../utils'; /** * Individual moment view opened when user clicks on a moment tile */ + +type MomentContextType = { + keyboardVisible: boolean; + scrollTo: (index: number) => void; +}; + +export const MomentContext = React.createContext({} as MomentContextType); + type IndividualMomentRouteProp = RouteProp<MainStackParams, 'IndividualMoment'>; + type IndividualMomentNavigationProp = StackNavigationProp< MainStackParams, 'IndividualMoment' >; + interface IndividualMomentProps { route: IndividualMomentRouteProp; navigation: IndividualMomentNavigationProp; } -const ITEM_HEIGHT = SCREEN_HEIGHT * 0.9; - const IndividualMoment: React.FC<IndividualMomentProps> = ({ route, navigation, }) => { - const {moment_category, moment_id} = route.params.moment; - const {userXId, screenType} = route.params; - + const { + userXId, + screenType, + moment: {moment_category, moment_id}, + } = route.params; const {moments} = useSelector((state: RootState) => userXId ? state.userX[screenType][userXId] : state.moments, ); - + const scrollRef = useRef<FlatList<MomentPostType>>(null); const momentData = moments.filter( (m) => m.moment_category === moment_category, ); const initialIndex = momentData.findIndex((m) => m.moment_id === moment_id); + const [keyboardVisible, setKeyboardVisible] = useState(false); + + useEffect(() => { + const showKeyboard = () => setKeyboardVisible(true); + const hideKeyboard = () => setKeyboardVisible(false); + Keyboard.addListener('keyboardWillShow', showKeyboard); + Keyboard.addListener('keyboardWillHide', hideKeyboard); + return () => { + Keyboard.removeListener('keyboardWillShow', showKeyboard); + Keyboard.removeListener('keyboardWillHide', hideKeyboard); + }; + }, []); + + const scrollTo = (index: number) => { + // TODO: make this dynamic + const offset = isIPhoneX() ? -(AVATAR_DIM + 100) : -(AVATAR_DIM + 160); + scrollRef.current?.scrollToIndex({ + index: index, + viewOffset: offset, + }); + }; return ( - <BlurView - blurType="light" - blurAmount={30} - reducedTransparencyFallbackColor="white" - style={styles.contentContainer}> - <IndividualMomentTitleBar - style={styles.header} - close={() => navigation.pop()} - {...{title: moment_category}} - /> - <View style={styles.content}> + <MomentContext.Provider + value={{ + keyboardVisible, + scrollTo, + }}> + <BlurView + blurType="light" + blurAmount={30} + reducedTransparencyFallbackColor="white" + style={styles.contentContainer}> + <IndividualMomentTitleBar + style={styles.header} + close={() => navigation.goBack()} + title={moment_category} + /> <FlatList + ref={scrollRef} data={momentData} - renderItem={({item}: {item: MomentType}) => ( - <MomentPost userXId={userXId} screenType={screenType} item={item} /> + contentContainerStyle={styles.listContentContainer} + renderItem={({item, index}) => ( + <MomentPost + moment={item} + userXId={userXId} + screenType={screenType} + index={index} + /> )} - keyExtractor={(item, index) => index.toString()} + keyExtractor={(item, _) => item.moment_id} showsVerticalScrollIndicator={false} - snapToAlignment={'start'} - snapToInterval={ITEM_HEIGHT} - decelerationRate={'fast'} initialScrollIndex={initialIndex} - getItemLayout={(data, index) => ({ - length: ITEM_HEIGHT, - offset: ITEM_HEIGHT * index, - index, - })} - pagingEnabled + onScrollToIndexFailed={() => { + // TODO: code below does not work, index resets to 0 + // const wait = new Promise((resolve) => setTimeout(resolve, 500)); + // wait.then(() => { + // console.log('scrolling to ', initialIndex); + // scrollRef.current?.scrollToIndex({index: initialIndex}); + // }); + }} /> - </View> - </BlurView> + </BlurView> + </MomentContext.Provider> ); }; const styles = StyleSheet.create({ contentContainer: { - width: SCREEN_WIDTH, - height: SCREEN_HEIGHT, paddingTop: StatusBarHeight, flex: 1, - paddingBottom: 0, - }, - content: { - flex: 9, }, header: { - flex: 1, - }, - postContainer: { - height: ITEM_HEIGHT, - width: SCREEN_WIDTH, - flex: 1, + height: normalize(70), }, - postHeader: { - flex: 1, + listContentContainer: { + paddingBottom: SCREEN_HEIGHT * 0.2, }, - postContent: {flex: 9}, }); export default IndividualMoment; diff --git a/src/screens/profile/InviteFriendsScreen.tsx b/src/screens/profile/InviteFriendsScreen.tsx index 89f2e62f..9ee6fb1c 100644 --- a/src/screens/profile/InviteFriendsScreen.tsx +++ b/src/screens/profile/InviteFriendsScreen.tsx @@ -21,7 +21,6 @@ import { getRemainingInviteCount, usersFromContactsService, } from '../../services/UserFriendsService'; -import {ProfilePreviewType} from '../../types'; import { extractContacts, HeaderHeight, @@ -45,11 +44,10 @@ export type SearchResultType = { const InviteFriendsScreen: React.FC = () => { const navigation = useNavigation(); - const [usersFromContacts, setUsersFromContacts] = useState< - ProfilePreviewType[] + const [nonUsersFromContacts, setNonUsersFromContacts] = useState< + InviteContactType[] >([]); - const [nonUsersFromContacts, setNonUsersFromContacts] = useState<[]>([]); - const [pendingUsers] = useState<[]>([]); + const [pendingUsers, setPendingUsers] = useState<InviteContactType[]>([]); const [results, setResults] = useState<SearchResultType>({ nonUsersFromContacts: nonUsersFromContacts, pendingUsers: pendingUsers, @@ -80,8 +78,8 @@ const InviteFriendsScreen: React.FC = () => { const permission = await checkPermission(); if (permission === 'authorized') { let response = await usersFromContactsService(retrievedContacts); - await setUsersFromContacts(response.existing_tagg_users); await setNonUsersFromContacts(response.invite_from_contacts); + await setPendingUsers(response.pending_users); setResults({ nonUsersFromContacts: response.invite_from_contacts, pendingUsers: response.pending_users, @@ -100,30 +98,32 @@ const InviteFriendsScreen: React.FC = () => { useEffect(() => { const search = async () => { if (query.length > 0) { - const searchResultsUsers = usersFromContacts.filter( - (item: ProfilePreviewType) => - (item.first_name + ' ' + item.last_name) - .toLowerCase() - .startsWith(query) || - item.username.toLowerCase().startsWith(query) || - item.last_name.toLowerCase().startsWith(query), - ); - const searchResultsNonUsers = nonUsersFromContacts.filter( - (item: InviteContactType) => - (item.firstName + ' ' + item.lastName) - .toLowerCase() - .startsWith(query) || - item.lastName.toLowerCase().startsWith(query), - ); - const sanitizedResult = { - usersFromContacts: searchResultsUsers, + const searchResultsPendingUsers = pendingUsers + ? pendingUsers.filter( + (item: InviteContactType) => + (item.firstName + ' ' + item.lastName) + .toLowerCase() + .startsWith(query) || + item.lastName.toLowerCase().startsWith(query), + ) + : []; + const searchResultsNonUsers = nonUsersFromContacts + ? nonUsersFromContacts.filter( + (item: InviteContactType) => + (item.firstName + ' ' + item.lastName) + .toLowerCase() + .startsWith(query) || + item.lastName.toLowerCase().startsWith(query), + ) + : []; + setResults({ nonUsersFromContacts: searchResultsNonUsers, - }; - setResults(sanitizedResult); + pendingUsers: searchResultsPendingUsers, + }); } else { setResults({ - nonUsersFromContacts: nonUsersFromContacts, - pendingUsers: pendingUsers, + nonUsersFromContacts: nonUsersFromContacts || [], + pendingUsers: pendingUsers || [], }); } }; @@ -203,7 +203,7 @@ const InviteFriendsScreen: React.FC = () => { styles.subheader, { height: - 72 * + 75 * (results.pendingUsers ? results.pendingUsers.length : 1), }, ]}> diff --git a/src/screens/profile/MomentCommentsScreen.tsx b/src/screens/profile/MomentCommentsScreen.tsx index 402e5f44..7dfe8ae9 100644 --- a/src/screens/profile/MomentCommentsScreen.tsx +++ b/src/screens/profile/MomentCommentsScreen.tsx @@ -48,8 +48,9 @@ const MomentCommentsScreen: React.FC<MomentCommentsScreenProps> = ({route}) => { React.useState(true); //Keeps track of the current comments object in focus so that the application knows which comment to post a reply to - const [commentTapped, setCommentTapped] = - useState<CommentType | CommentThreadType | undefined>(); + const [commentTapped, setCommentTapped] = useState< + CommentType | CommentThreadType | undefined + >(); useEffect(() => { navigation.setOptions({ |
