diff options
Diffstat (limited to 'src/screens')
| -rw-r--r-- | src/screens/chat/ChatListScreen.tsx | 134 | ||||
| -rw-r--r-- | src/screens/chat/ChatResultsCell.tsx | 117 | ||||
| -rw-r--r-- | src/screens/chat/ChatResultsList.tsx | 102 | ||||
| -rw-r--r-- | src/screens/chat/ChatScreen.tsx | 53 | ||||
| -rw-r--r-- | src/screens/chat/ChatSearchBar.tsx | 112 | ||||
| -rw-r--r-- | src/screens/chat/NewChatModal.tsx | 161 | ||||
| -rw-r--r-- | src/screens/chat/index.ts | 6 | ||||
| -rw-r--r-- | src/screens/index.ts | 1 | ||||
| -rw-r--r-- | src/screens/main/NotificationsScreen.tsx | 2 | ||||
| -rw-r--r-- | src/screens/onboarding/Login.tsx | 1 | ||||
| -rw-r--r-- | src/screens/profile/IndividualMoment.tsx | 14 | ||||
| -rw-r--r-- | src/screens/profile/InviteFriendsScreen.tsx | 2 | ||||
| -rw-r--r-- | src/screens/profile/MomentUploadPromptScreen.tsx | 20 | ||||
| -rw-r--r-- | src/screens/profile/ProfileScreen.tsx | 36 | ||||
| -rw-r--r-- | src/screens/profile/SettingsScreen.tsx | 6 | ||||
| -rw-r--r-- | src/screens/search/DiscoverUsers.tsx | 2 | ||||
| -rw-r--r-- | src/screens/search/SearchScreen.tsx | 180 | ||||
| -rw-r--r-- | src/screens/suggestedPeople/SPBody.tsx | 20 | ||||
| -rw-r--r-- | src/screens/suggestedPeople/SuggestedPeopleScreen.tsx | 2 |
19 files changed, 776 insertions, 195 deletions
diff --git a/src/screens/chat/ChatListScreen.tsx b/src/screens/chat/ChatListScreen.tsx new file mode 100644 index 00000000..daea9984 --- /dev/null +++ b/src/screens/chat/ChatListScreen.tsx @@ -0,0 +1,134 @@ +import AsyncStorage from '@react-native-community/async-storage'; +import {StackNavigationProp} from '@react-navigation/stack'; +import React, {useContext, useEffect, useMemo, useState} from 'react'; +import {SafeAreaView, StatusBar, StyleSheet, View} from 'react-native'; +import {useStore} from 'react-redux'; +import {ChannelList, Chat} from 'stream-chat-react-native'; +import {ChatContext} from '../../App'; +import {TabsGradient} from '../../components'; +import {ChannelPreview, MessagesHeader} from '../../components/messages'; +import {MainStackParams} from '../../routes'; +import {RootState} from '../../store/rootReducer'; +import { + LocalAttachmentType, + LocalChannelType, + LocalCommandType, + LocalEventType, + LocalMessageType, + LocalReactionType, + LocalUserType, +} from '../../types'; + +import NewChatModal from './NewChatModal'; +type ChatListScreenNavigationProp = StackNavigationProp< + MainStackParams, + 'ChatList' +>; +interface ChatListScreenProps { + navigation: ChatListScreenNavigationProp; +} +/* + * Screen that displays all of the user's active conversations. + */ +const ChatListScreen: React.FC<ChatListScreenProps> = ({navigation}) => { + const {chatClient, setChannel} = useContext(ChatContext); + const [modalVisible, setChatModalVisible] = useState(false); + + const [clientReady, setClientReady] = useState(false); + const state: RootState = useStore().getState(); + const loggedInUserId = state.user.user.userId; + + const memoizedFilters = useMemo( + () => ({ + members: {$in: [loggedInUserId]}, + type: 'messaging', + }), + [], + ); + + useEffect(() => { + const setupClient = async () => { + const chatToken = await AsyncStorage.getItem('chatToken'); + await chatClient.connectUser( + { + id: loggedInUserId, + }, + chatToken, + ); + return setClientReady(true); + }; + if (!clientReady) { + setupClient().catch((err) => { + console.error(err); + }); + } + }, []); + + return ( + <View style={styles.background}> + <SafeAreaView> + <StatusBar barStyle="dark-content" /> + <MessagesHeader + createChannel={() => { + setChatModalVisible(true); + }} + /> + {clientReady && ( + <Chat client={chatClient}> + <View style={styles.chatContainer}> + <ChannelList< + LocalAttachmentType, + LocalChannelType, + LocalCommandType, + LocalEventType, + LocalMessageType, + LocalReactionType, + LocalUserType + > + filters={memoizedFilters} + options={{ + presence: true, + state: true, + watch: true, + }} + sort={{last_message_at: -1}} + maxUnreadCount={99} + Preview={ChannelPreview} + /> + </View> + </Chat> + )} + <NewChatModal {...{modalVisible, setChatModalVisible}} /> + </SafeAreaView> + <TabsGradient /> + </View> + ); +}; + +const styles = StyleSheet.create({ + background: { + flex: 1, + backgroundColor: 'white', + }, + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + placeholder: { + fontSize: 14, + fontWeight: 'bold', + marginBottom: 10, + }, + button: { + backgroundColor: '#CCE4FC', + padding: 15, + borderRadius: 5, + }, + chatContainer: { + height: '100%', + marginTop: 10, + }, +}); + +export default ChatListScreen; diff --git a/src/screens/chat/ChatResultsCell.tsx b/src/screens/chat/ChatResultsCell.tsx new file mode 100644 index 00000000..d947c122 --- /dev/null +++ b/src/screens/chat/ChatResultsCell.tsx @@ -0,0 +1,117 @@ +import {useNavigation} from '@react-navigation/native'; +import React, {useContext, useEffect, useState} from 'react'; +import {Alert, Image, StyleSheet, Text, View} from 'react-native'; +import {TouchableOpacity} from 'react-native-gesture-handler'; +import {ChatContext} from '../../App'; +import {ERROR_FAILED_TO_CREATE_CHANNEL} from '../../constants/strings'; +import {loadImageFromURL} from '../../services'; +import {ProfilePreviewType, UserType} from '../../types'; +import {createChannel, normalize, SCREEN_WIDTH} from '../../utils'; +import {defaultUserProfile} from '../../utils/users'; + +interface ChatResults { + profileData: ProfilePreviewType; + loggedInUser: UserType; + setChatModalVisible: Function; +} + +const ChatResultsCell: React.FC<ChatResults> = ({ + profileData: {id, username, first_name, last_name, thumbnail_url}, + loggedInUser, + setChatModalVisible, +}) => { + const [avatar, setAvatar] = useState<string | undefined>(undefined); + const {chatClient, setChannel} = useContext(ChatContext); + + useEffect(() => { + (async () => { + if (thumbnail_url !== undefined) { + try { + const response = await loadImageFromURL(thumbnail_url); + if (response) { + setAvatar(response); + } + } catch (error) { + console.log('Error while downloading ', error); + throw error; + } + } + })(); + }, [thumbnail_url]); + + const navigation = useNavigation(); + const createChannelIfNotPresentAndNavigate = async () => { + try { + setChatModalVisible(false); + const channel = await createChannel(loggedInUser.userId, id, chatClient); + setChannel(channel); + setTimeout(() => { + navigation.navigate('Chat'); + }, 100); + } catch (error) { + Alert.alert(ERROR_FAILED_TO_CREATE_CHANNEL); + } + }; + + const userCell = () => { + return ( + <TouchableOpacity + onPress={createChannelIfNotPresentAndNavigate} + style={styles.cellContainer}> + <Image + defaultSource={defaultUserProfile()} + source={{uri: avatar}} + style={styles.imageContainer} + /> + <View style={[styles.initialTextContainer, styles.multiText]}> + <Text style={styles.initialTextStyle}>{`@${username}`}</Text> + <Text style={styles.secondaryTextStyle}> + {first_name + ' ' + last_name} + </Text> + </View> + </TouchableOpacity> + ); + }; + + return userCell(); +}; + +const styles = StyleSheet.create({ + cellContainer: { + flexDirection: 'row', + paddingHorizontal: 25, + paddingVertical: 15, + width: SCREEN_WIDTH, + }, + imageContainer: { + width: SCREEN_WIDTH * 0.112, + height: SCREEN_WIDTH * 0.112, + borderRadius: (SCREEN_WIDTH * 0.112) / 2, + }, + categoryBackground: { + backgroundColor: 'rgba(196, 196, 196, 0.45)', + justifyContent: 'center', + alignItems: 'center', + }, + categoryImage: { + width: '40%', + height: '40%', + }, + initialTextContainer: { + marginLeft: SCREEN_WIDTH * 0.08, + flexDirection: 'column', + justifyContent: 'center', + }, + initialTextStyle: { + fontWeight: '500', + fontSize: normalize(14), + }, + secondaryTextStyle: { + fontWeight: '500', + fontSize: normalize(12), + color: '#828282', + }, + multiText: {justifyContent: 'space-between'}, +}); + +export default ChatResultsCell; diff --git a/src/screens/chat/ChatResultsList.tsx b/src/screens/chat/ChatResultsList.tsx new file mode 100644 index 00000000..b9970772 --- /dev/null +++ b/src/screens/chat/ChatResultsList.tsx @@ -0,0 +1,102 @@ +import {useBottomTabBarHeight} from '@react-navigation/bottom-tabs'; +import React, {useEffect, useState} from 'react'; +import { + Keyboard, + SectionList, + SectionListData, + StyleSheet, + Text, + View, +} from 'react-native'; +import {useSelector} from 'react-redux'; +import {NO_RESULTS_FOUND} from '../../constants/strings'; +import {RootState} from '../../store/rootreducer'; +import {PreviewType, ScreenType} from '../../types'; +import {normalize, SCREEN_WIDTH} from '../../utils'; +import ChatResultsCell from './ChatResultsCell'; + +interface ChatResultsProps { + // TODO: make sure results come in as same type, regardless of profile, category, badges + results: SectionListData<any>[]; + previewType: PreviewType; + screenType: ScreenType; + setChatModalVisible: Function; +} + +const ChatResultsList: React.FC<ChatResultsProps> = ({ + results, + setChatModalVisible, +}) => { + const [showEmptyView, setshowEmptyView] = useState<boolean>(false); + const {user: loggedInUser} = useSelector((state: RootState) => state.user); + const tabbarHeight = useBottomTabBarHeight(); + + useEffect(() => { + if (results && results.length > 0) { + let showEmpty = true; + + results.forEach((e) => { + if (e.data.length > 0) { + showEmpty = false; + } + }); + setshowEmptyView(showEmpty); + } + }, [results]); + + return showEmptyView ? ( + <View style={styles.container} onTouchStart={Keyboard.dismiss}> + <Text style={styles.noResultsTextStyle}>{NO_RESULTS_FOUND}</Text> + </View> + ) : ( + <SectionList + onScrollBeginDrag={Keyboard.dismiss} + contentContainerStyle={[{paddingBottom: tabbarHeight}]} + sections={results} + keyExtractor={(item, index) => item.id + index} + renderItem={({item}) => ( + <ChatResultsCell + profileData={item} + setChatModalVisible={setChatModalVisible} + loggedInUser={loggedInUser} + /> + )} + stickySectionHeadersEnabled={false} + ListEmptyComponent={() => ( + <View style={styles.empty}> + <Text>Start a new chat by searching for someone</Text> + </View> + )} + /> + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + marginTop: 30, + alignItems: 'center', + }, + sectionHeaderStyle: { + width: '100%', + height: 0.5, + marginVertical: 5, + backgroundColor: '#C4C4C4', + }, + noResultsTextContainer: { + justifyContent: 'center', + flexDirection: 'row', + width: SCREEN_WIDTH, + }, + noResultsTextStyle: { + fontWeight: '500', + fontSize: normalize(14), + }, + empty: { + marginTop: 20, + justifyContent: 'center', + alignItems: 'center', + }, +}); + +export default ChatResultsList; diff --git a/src/screens/chat/ChatScreen.tsx b/src/screens/chat/ChatScreen.tsx new file mode 100644 index 00000000..59c53c99 --- /dev/null +++ b/src/screens/chat/ChatScreen.tsx @@ -0,0 +1,53 @@ +import {useBottomTabBarHeight} from '@react-navigation/bottom-tabs'; +import {StackNavigationProp} from '@react-navigation/stack'; +import React, {useContext} from 'react'; +import {StyleSheet} from 'react-native'; +import {SafeAreaView} from 'react-native-safe-area-context'; +import { + Channel, + Chat, + MessageInput, + MessageList, +} from 'stream-chat-react-native'; +import {ChatContext} from '../../App'; +import ChatHeader from '../../components/messages/ChatHeader'; +import {MainStackParams} from '../../routes'; +import {isIPhoneX} from '../../utils'; + +type ChatScreenNavigationProp = StackNavigationProp<MainStackParams, 'Chat'>; +interface ChatScreenProps { + navigation: ChatScreenNavigationProp; +} +/* + * Screen that displays all of the user's active conversations. + */ +const ChatScreen: React.FC<ChatScreenProps> = () => { + const {channel, chatClient} = useContext(ChatContext); + const tabbarHeight = useBottomTabBarHeight(); + + return ( + <SafeAreaView + style={[ + styles.container, + // unable to figure out the padding issue, a hacky solution + {paddingBottom: isIPhoneX() ? tabbarHeight + 20 : tabbarHeight + 50}, + ]}> + <ChatHeader /> + <Chat client={chatClient}> + <Channel channel={channel} keyboardVerticalOffset={0}> + <MessageList onThreadSelect={() => {}} /> + <MessageInput /> + </Channel> + </Chat> + </SafeAreaView> + ); +}; + +const styles = StyleSheet.create({ + container: { + backgroundColor: 'white', + flex: 1, + }, +}); + +export default ChatScreen; diff --git a/src/screens/chat/ChatSearchBar.tsx b/src/screens/chat/ChatSearchBar.tsx new file mode 100644 index 00000000..4916ec45 --- /dev/null +++ b/src/screens/chat/ChatSearchBar.tsx @@ -0,0 +1,112 @@ +import React from 'react'; +import { + Keyboard, + NativeSyntheticEvent, + StyleSheet, + Text, + TextInput, + TextInputProps, + TextInputSubmitEditingEventData, + TouchableOpacity, + View, + ViewStyle, +} from 'react-native'; +import {normalize} from 'react-native-elements'; +import Animated, {useAnimatedStyle} from 'react-native-reanimated'; + +interface SearchBarProps extends TextInputProps { + onCancel: () => void; + animationProgress: Animated.SharedValue<number>; + searching: boolean; + placeholder: string; +} +const ChatSearchBar: React.FC<SearchBarProps> = ({ + onFocus, + onBlur, + onChangeText, + value, + onCancel, + searching, + animationProgress, + onLayout, + placeholder, +}) => { + const handleSubmit = ( + e: NativeSyntheticEvent<TextInputSubmitEditingEventData>, + ) => { + e.preventDefault(); + Keyboard.dismiss(); + }; + + /* + * On-search marginRight style ("cancel" button slides and fades in). + */ + const animatedStyles = useAnimatedStyle<ViewStyle>(() => ({ + marginRight: animationProgress.value * 58, + opacity: animationProgress.value, + })); + + return ( + <View style={styles.container} onLayout={onLayout}> + <Animated.View style={styles.inputContainer}> + <Animated.View style={styles.searchTextContainer}> + <Text style={styles.searchTextStyes}>To:</Text> + </Animated.View> + <TextInput + style={[styles.input]} + placeholderTextColor={'#828282'} + onSubmitEditing={handleSubmit} + clearButtonMode="always" + autoCapitalize="none" + autoCorrect={false} + {...{placeholder, value, onChangeText, onFocus, onBlur}} + /> + </Animated.View> + <Animated.View style={animatedStyles}> + <TouchableOpacity style={styles.cancelButton} onPress={onCancel}> + <Text style={styles.cancelText}>Cancel</Text> + </TouchableOpacity> + </Animated.View> + </View> + ); +}; + +const styles = StyleSheet.create({ + container: { + height: 40, + paddingHorizontal: 20, + flexDirection: 'row', + zIndex: 2, + }, + searchTextContainer: {marginHorizontal: 12}, + searchTextStyes: {fontWeight: 'bold', fontSize: 14, lineHeight: 17}, + inputContainer: { + flexGrow: 1, + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 8, + borderRadius: 20, + backgroundColor: '#F0F0F0', + }, + searchIcon: { + marginRight: 8, + }, + input: { + flex: 1, + fontSize: 16, + color: '#000', + letterSpacing: normalize(0.5), + }, + cancelButton: { + height: '100%', + position: 'absolute', + justifyContent: 'center', + paddingHorizontal: 8, + }, + cancelText: { + color: '#818181', + fontWeight: '500', + }, +}); + +export default ChatSearchBar; diff --git a/src/screens/chat/NewChatModal.tsx b/src/screens/chat/NewChatModal.tsx new file mode 100644 index 00000000..95e46ecd --- /dev/null +++ b/src/screens/chat/NewChatModal.tsx @@ -0,0 +1,161 @@ +import React, {useEffect, useState} from 'react'; +import { + Keyboard, + SectionListData, + StatusBar, + StyleSheet, + Text, + View, +} from 'react-native'; +import {useSharedValue} from 'react-native-reanimated'; +import {BottomDrawer} from '../../components'; +import { + SEARCH_ENDPOINT_MESSAGES, + SEARCH_ENDPOINT_SUGGESTED, +} from '../../constants'; +import {loadSearchResults} from '../../services'; +import {ScreenType} from '../../types'; +import {normalize} from '../../utils'; +import {ChatResultsList, ChatSearchBar} from './index'; +interface NewChatModalProps { + modalVisible: boolean; + setChatModalVisible: (open: boolean) => void; +} + +const NewChatModal: React.FC<NewChatModalProps> = ({ + modalVisible, + setChatModalVisible, +}) => { + const [searching, setSearching] = useState(false); + /* + * Animated value + */ + const animationProgress = useSharedValue<number>(0); + const [results, setResults] = useState<SectionListData<any>[]>([]); + const [query, setQuery] = useState<string>(''); + const handleFocus = () => { + setSearching(true); + }; + const handleBlur = () => { + Keyboard.dismiss(); + }; + const handleCancel = () => { + setSearching(false); + }; + + const getDefaultSuggested = async () => { + const searchResults = await loadSearchResults( + `${SEARCH_ENDPOINT_SUGGESTED}`, + ); + console.log(searchResults); + const sanitizedResult = [ + { + title: 'users', + data: searchResults?.users, + }, + ]; + console.log(searchResults, sanitizedResult); + setResults(sanitizedResult); + }; + + const getQuerySuggested = async () => { + const searchResults = await loadSearchResults( + `${SEARCH_ENDPOINT_MESSAGES}?query=${query}`, + ); + if (query.length > 2) { + const sanitizedResult = [ + { + title: 'users', + data: searchResults?.users, + }, + ]; + setResults(sanitizedResult); + } else { + setResults([]); + } + }; + + useEffect(() => { + if (query.length === 0) { + getDefaultSuggested(); + } + + if (!searching) { + return; + } + + if (query.length < 3) { + return; + } + getQuerySuggested(); + }, [query]); + + const _modalContent = () => { + return ( + <View style={styles.modalShadowContainer}> + <View style={styles.titleContainerStyles}> + <Text style={styles.titleTextStyles}>New Message</Text> + </View> + <ChatSearchBar + onCancel={handleCancel} + onChangeText={setQuery} + onBlur={handleBlur} + onFocus={handleFocus} + value={query} + {...{animationProgress, searching}} + placeholder={''} + /> + {results.length > 0 && ( + <View style={styles.headerContainerStyles}> + <Text style={styles.headerTextStyles}>Suggested</Text> + </View> + )} + <ChatResultsList + {...{results, setChatModalVisible}} + previewType={'Search'} + screenType={ScreenType.Search} + /> + </View> + ); + }; + + return ( + <View> + <StatusBar barStyle="dark-content" /> + <BottomDrawer + initialSnapPosition={'90%'} + isOpen={modalVisible} + setIsOpen={setChatModalVisible} + showHeader={false}> + {_modalContent()} + </BottomDrawer> + </View> + ); +}; + +const styles = StyleSheet.create({ + modalShadowContainer: { + height: '100%', + borderRadius: 9, + backgroundColor: 'white', + }, + titleContainerStyles: {marginVertical: 24}, + titleTextStyles: { + fontWeight: 'bold', + fontSize: normalize(18), + lineHeight: normalize(21), + textAlign: 'center', + }, + headerContainerStyles: { + marginTop: 26, + marginBottom: 10, + marginHorizontal: 28, + }, + headerTextStyles: { + fontWeight: 'bold', + fontSize: normalize(17), + lineHeight: normalize(20), + }, +}); + +export default NewChatModal; diff --git a/src/screens/chat/index.ts b/src/screens/chat/index.ts new file mode 100644 index 00000000..328eb8bf --- /dev/null +++ b/src/screens/chat/index.ts @@ -0,0 +1,6 @@ +export {default as ChatListScreen} from './ChatListScreen'; +export {default as ChatScreen} from './ChatScreen'; +export {default as NewChatModal} from './NewChatModal'; +export {default as ChatSearchBar} from './ChatSearchBar'; +export {default as ChatResultsList} from './ChatResultsList'; +export {default as ChatResultsCell} from './ChatResultsCell'; diff --git a/src/screens/index.ts b/src/screens/index.ts index 50ada3d1..44ae4b52 100644 --- a/src/screens/index.ts +++ b/src/screens/index.ts @@ -5,3 +5,4 @@ export * from './search'; export * from './suggestedPeople'; export * from './suggestedPeopleOnboarding'; export * from './badge'; +export * from './chat'; diff --git a/src/screens/main/NotificationsScreen.tsx b/src/screens/main/NotificationsScreen.tsx index 48e89f7a..71199c9b 100644 --- a/src/screens/main/NotificationsScreen.tsx +++ b/src/screens/main/NotificationsScreen.tsx @@ -330,7 +330,7 @@ const styles = StyleSheet.create({ flexDirection: 'row', alignItems: 'stretch', justifyContent: 'space-between', - width: SCREEN_WIDTH * 0.85, + width: SCREEN_WIDTH * 0.9, }, headerText: { fontWeight: '700', diff --git a/src/screens/onboarding/Login.tsx b/src/screens/onboarding/Login.tsx index 49ca5ff4..dd2bb2e4 100644 --- a/src/screens/onboarding/Login.tsx +++ b/src/screens/onboarding/Login.tsx @@ -160,6 +160,7 @@ const Login: React.FC<LoginProps> = ({navigation}: LoginProps) => { await AsyncStorage.setItem('token', data.token); await AsyncStorage.setItem('userId', data.UserID); await AsyncStorage.setItem('username', username); + await AsyncStorage.setItem('chatToken', data.chatToken); } if (statusCode === 200 && data.isOnboarded) { diff --git a/src/screens/profile/IndividualMoment.tsx b/src/screens/profile/IndividualMoment.tsx index 8c1dc327..871d62bf 100644 --- a/src/screens/profile/IndividualMoment.tsx +++ b/src/screens/profile/IndividualMoment.tsx @@ -27,7 +27,7 @@ interface IndividualMomentProps { navigation: IndividualMomentNavigationProp; } -const ITEM_HEIGHT = SCREEN_HEIGHT * (9 / 10); +const ITEM_HEIGHT = SCREEN_HEIGHT * 0.9; const IndividualMoment: React.FC<IndividualMomentProps> = ({ route, @@ -40,13 +40,13 @@ const IndividualMoment: React.FC<IndividualMomentProps> = ({ ); const { user: {username}, - } = userXId - ? useSelector((state: RootState) => state.userX[screenType][userXId]) - : useSelector((state: RootState) => state.user); + } = useSelector((state: RootState) => + userXId ? state.userX[screenType][userXId] : state.user, + ); - const {moments} = userXId - ? useSelector((state: RootState) => state.userX[screenType][userXId]) - : useSelector((state: RootState) => state.moments); + const {moments} = useSelector((state: RootState) => + userXId ? state.userX[screenType][userXId] : state.moments, + ); const isOwnProfile = username === loggedInUsername; const momentData = moments.filter( diff --git a/src/screens/profile/InviteFriendsScreen.tsx b/src/screens/profile/InviteFriendsScreen.tsx index a9fa1404..ad9e382e 100644 --- a/src/screens/profile/InviteFriendsScreen.tsx +++ b/src/screens/profile/InviteFriendsScreen.tsx @@ -203,7 +203,7 @@ const InviteFriendsScreen: React.FC<InviteFriendsScreenProps> = ({route}) => { </Animated.View> </View> <View style={styles.subheader}> - <Text style={styles.subheaderText}>Contacts on tagg</Text> + <Text style={styles.subheaderText}>Contacts on Tagg</Text> <UsersFromContacts /> </View> <View style={styles.subheader}> diff --git a/src/screens/profile/MomentUploadPromptScreen.tsx b/src/screens/profile/MomentUploadPromptScreen.tsx index f79c81b4..f0aaffc4 100644 --- a/src/screens/profile/MomentUploadPromptScreen.tsx +++ b/src/screens/profile/MomentUploadPromptScreen.tsx @@ -8,7 +8,7 @@ import {Moment} from '../../components'; import {Image} from 'react-native-animatable'; import {UPLOAD_MOMENT_PROMPT_ONE_MESSAGE} from '../../constants/strings'; import {PROFILE_CUTOUT_BOTTOM_Y} from '../../constants'; -import {isIPhoneX, normalize} from '../../utils'; +import {normalize} from '../../utils'; type MomentUploadPromptScreenRouteProp = RouteProp< MainStackParams, @@ -28,7 +28,12 @@ const MomentUploadPromptScreen: React.FC<MomentUploadPromptScreenProps> = ({ route, navigation, }) => { - const {screenType, momentCategory, profileBodyHeight} = route.params; + const { + screenType, + momentCategory, + profileBodyHeight, + socialsBarHeight, + } = route.params; return ( <View style={styles.container}> <CloseIcon @@ -61,9 +66,7 @@ const MomentUploadPromptScreen: React.FC<MomentUploadPromptScreenProps> = ({ externalStyles={{ container: { ...styles.momentContainer, - top: isIPhoneX() - ? profileBodyHeight + 615 - : profileBodyHeight + 500, + top: PROFILE_CUTOUT_BOTTOM_Y + profileBodyHeight + socialsBarHeight, }, titleText: styles.momentHeaderText, header: styles.momentHeader, @@ -103,20 +106,21 @@ const styles = StyleSheet.create({ //Styles to adjust moment container momentScrollContainer: { backgroundColor: 'transparent', + marginTop: 10, }, momentContainer: { ...StyleSheet.absoluteFillObject, backgroundColor: 'transparent', - height: 170, + height: 175, }, momentHeaderText: { ...StyleSheet.absoluteFillObject, marginLeft: 12, - marginTop: 10, + paddingVertical: 5, }, momentHeader: { + marginTop: 7, backgroundColor: 'transparent', - paddingVertical: 20, }, }); diff --git a/src/screens/profile/ProfileScreen.tsx b/src/screens/profile/ProfileScreen.tsx index 313e2f2c..6d9ef020 100644 --- a/src/screens/profile/ProfileScreen.tsx +++ b/src/screens/profile/ProfileScreen.tsx @@ -1,17 +1,8 @@ import React from 'react'; import {StatusBar} from 'react-native'; -import Animated from 'react-native-reanimated'; -import {Content, Cover, TabsGradient} from '../../components'; -import {RouteProp, useFocusEffect} from '@react-navigation/native'; +import {Content, TabsGradient} from '../../components'; +import {RouteProp} from '@react-navigation/native'; import {MainStackParams} from '../../routes/'; -import {resetScreenType} from '../../store/actions'; -import {useDispatch, useStore} from 'react-redux'; -import {DUMMY_USERID} from '../../store/initialStates'; - -/**r - * Profile Screen for a user's profile - * including posts, messaging, and settings - */ type ProfileScreenRouteProps = RouteProp<MainStackParams, 'Profile'>; @@ -22,32 +13,11 @@ interface ProfileOnboardingProps { const ProfileScreen: React.FC<ProfileOnboardingProps> = ({route}) => { const {screenType} = route.params; let {userXId} = route.params; - const y = Animated.useValue(0); - const dispatch = useDispatch(); - - /** - * This is a double safety check to avoid app crash. - * Checks if the required userXId is present in the store, if not userXId is set to dummy id - */ - // if (userXId && !(userXId in useStore().getState().userX[screenType])) { - // userXId = DUMMY_USERID; - // } - - /** - * Code under useFocusEffect gets executed every time the screen comes under focus / is being viewed by the user. - * This is done to reset the users stored in our store for the Search screen. - * Read more about useFocusEffect here : https://reactnavigation.org/docs/function-after-focusing-screen/ - */ - // useFocusEffect(() => { - // if (!userXId) { - // dispatch(resetScreenType(screenType)); - // } - // }); return ( <> <StatusBar barStyle="dark-content" /> - <Content {...{y, userXId, screenType}} /> + <Content {...{userXId, screenType}} /> <TabsGradient /> </> ); diff --git a/src/screens/profile/SettingsScreen.tsx b/src/screens/profile/SettingsScreen.tsx index 05e051b5..ecc3bafd 100644 --- a/src/screens/profile/SettingsScreen.tsx +++ b/src/screens/profile/SettingsScreen.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useContext} from 'react'; import { SafeAreaView, SectionList, @@ -17,6 +17,7 @@ import {BackgroundGradientType} from '../../types'; import {normalize, SCREEN_HEIGHT} from '../../utils/layouts'; import SettingsCell from './SettingsCell'; import {useNavigation} from '@react-navigation/core'; +import {ChatContext} from '../../App'; const SettingsScreen: React.FC = () => { const dispatch = useDispatch(); @@ -24,6 +25,7 @@ const SettingsScreen: React.FC = () => { const {suggested_people_linked} = useSelector( (state: RootState) => state.user.profile, ); + const {chatClient} = useContext(ChatContext); return ( <> @@ -49,7 +51,7 @@ const SettingsScreen: React.FC = () => { <TouchableOpacity style={styles.logoutContainerStyles} onPress={() => { - dispatch(logout()); + dispatch(logout(chatClient)); navigation.reset({ index: 0, routes: [{name: 'SuggestedPeople'}], diff --git a/src/screens/search/DiscoverUsers.tsx b/src/screens/search/DiscoverUsers.tsx index b87bfc37..f67585f2 100644 --- a/src/screens/search/DiscoverUsers.tsx +++ b/src/screens/search/DiscoverUsers.tsx @@ -126,7 +126,7 @@ const DiscoverUsers: React.FC<DiscoverUsersProps> = ({route}) => { ListFooterComponent={() => ( <> <Text style={styles.otherGroups}>Other Groups</Text> - <SearchCategories darkStyle={true} /> + <SearchCategories useSuggestions={true} darkStyle={true} /> </> )} /> diff --git a/src/screens/search/SearchScreen.tsx b/src/screens/search/SearchScreen.tsx index 4f0cabb4..f7e1c467 100644 --- a/src/screens/search/SearchScreen.tsx +++ b/src/screens/search/SearchScreen.tsx @@ -1,8 +1,19 @@ import AsyncStorage from '@react-native-community/async-storage'; import {useFocusEffect} from '@react-navigation/native'; import React, {useEffect, useState} from 'react'; -import {Keyboard, ScrollView, StatusBar, StyleSheet} from 'react-native'; -import Animated, {Easing, timing} from 'react-native-reanimated'; +import { + Keyboard, + StatusBar, + StyleSheet, + LayoutChangeEvent, + SectionListData, +} from 'react-native'; +import { + useSharedValue, + withTiming, + Easing, + runOnJS, +} from 'react-native-reanimated'; import {SafeAreaView} from 'react-native-safe-area-context'; import {useDispatch, useSelector} from 'react-redux'; import { @@ -13,22 +24,14 @@ import { SearchResultsBackground, TabsGradient, } from '../../components'; -import {SEARCH_ENDPOINT, TAGG_LIGHT_BLUE} from '../../constants'; +import {SEARCH_ENDPOINT} from '../../constants'; import {loadSearchResults} from '../../services'; import {resetScreenType} from '../../store/actions'; import {RootState} from '../../store/rootReducer'; -import { - CategoryPreviewType, - ProfilePreviewType, - ScreenType, - SearchCategoryType, -} from '../../types'; +import {CategoryPreviewType, ProfilePreviewType, ScreenType} from '../../types'; import { getRecentlySearchedCategories, getRecentlySearchedUsers, - normalize, - SCREEN_HEIGHT, - SCREEN_WIDTH, } from '../../utils'; /** @@ -38,11 +41,8 @@ import { const SearchScreen: React.FC = () => { const {recentSearches} = useSelector((state: RootState) => state.taggUsers); - const { - profile: {university = ''}, - } = useSelector((state: RootState) => state.user); const [query, setQuery] = useState<string>(''); - const [results, setResults] = useState<Array<any> | undefined>(undefined); + const [results, setResults] = useState<SectionListData<any>[] | undefined>(); const [recents, setRecents] = useState<Array<ProfilePreviewType>>( recentSearches ?? [], ); @@ -50,26 +50,12 @@ const SearchScreen: React.FC = () => { CategoryPreviewType[] >([]); const [searching, setSearching] = useState(false); - const top = Animated.useValue(-SCREEN_HEIGHT); - const defaultButtons: SearchCategoryType[] = [21, 22, 23, 24].map((year) => ({ - id: -1, - name: `${university.split(' ')[0]} '${year}`, - category: university, - })); - const [keyboardVisible, setKeyboardVisible] = React.useState( - 'keyboardVisible', - ); - useEffect(() => { - const showKeyboard = () => setKeyboardVisible('keyboardVisibleTrue'); - Keyboard.addListener('keyboardWillShow', showKeyboard); - return () => Keyboard.removeListener('keyboardWillShow', showKeyboard); - }, []); + /* + * Animated value + */ + const animationProgress = useSharedValue<number>(0); + const [searchBarHeight, setSearchBarHeight] = useState<number>(0); - useEffect(() => { - const hideKeyboard = () => setKeyboardVisible('keyboardVisibleFalse'); - Keyboard.addListener('keyboardWillHide', hideKeyboard); - return () => Keyboard.removeListener('keyboardWillHide', hideKeyboard); - }, []); const dispatch = useDispatch(); /* @@ -122,12 +108,22 @@ const SearchScreen: React.FC = () => { useEffect(() => { if (searching) { loadRecentlySearched().then(() => { - timing(top, topInConfig).start(); + animationProgress.value = withTiming(1, { + duration: 180, + easing: Easing.bezier(0.31, 0.14, 0.66, 0.82), + }); }); } else { setQuery(''); handleBlur(); - timing(top, topOutConfig).start(() => setResults(undefined)); + animationProgress.value = withTiming( + 0, + {duration: 180, easing: Easing.inOut(Easing.ease)}, + () => { + 'worklet'; + runOnJS(setResults)(undefined); + }, + ); } }, [searching]); @@ -153,16 +149,6 @@ const SearchScreen: React.FC = () => { } }; - const topInConfig = { - duration: 180, - toValue: 0, - easing: Easing.bezier(0.31, 0.14, 0.66, 0.82), - }; - const topOutConfig = { - duration: 180, - toValue: -SCREEN_HEIGHT, - easing: Easing.inOut(Easing.ease), - }; const handleFocus = () => { setSearching(true); }; @@ -172,9 +158,12 @@ const SearchScreen: React.FC = () => { const handleCancel = () => { setSearching(false); }; + const onSearchBarLayout = (e: LayoutChangeEvent) => { + setSearchBarHeight(e.nativeEvent.layout.height); + }; return ( - <SafeAreaView style={styles.screenContainer}> + <SafeAreaView style={styles.container}> <StatusBar barStyle="dark-content" /> <SearchBar onCancel={handleCancel} @@ -182,98 +171,39 @@ const SearchScreen: React.FC = () => { onBlur={handleBlur} onFocus={handleFocus} value={query} - {...{top, searching}} + onLayout={onSearchBarLayout} + {...{animationProgress, searching}} /> - <ScrollView - scrollEnabled={!searching} - keyboardShouldPersistTaps={'always'} - stickyHeaderIndices={[4]} - contentContainerStyle={styles.contentContainer} - showsVerticalScrollIndicator={false}> - <SearchCategories defaultButtons={defaultButtons} /> - <SearchResultsBackground {...{top}}> - {results === undefined && - recents.length + recentCategories.length !== 0 ? ( + <SearchCategories useSuggestions={false} /> + <SearchResultsBackground + {...{searching, searchBarHeight, animationProgress}}> + {results === undefined ? ( + recents.length + recentCategories.length > 0 && ( <RecentSearches sectionTitle="Recent" onPress={clearRecentlySearched} screenType={ScreenType.Search} {...{recents, recentCategories}} /> - ) : ( - <SearchResultList - {...{results}} - keyboardVisible={keyboardVisible === 'keyboardVisibleTrue'} - previewType={'Search'} - screenType={ScreenType.Search} - /> - )} - </SearchResultsBackground> - </ScrollView> + ) + ) : ( + <SearchResultList + {...{results}} + previewType={'Search'} + screenType={ScreenType.Search} + /> + )} + </SearchResultsBackground> <TabsGradient /> </SafeAreaView> ); }; const styles = StyleSheet.create({ - screenContainer: { + container: { + flex: 1, paddingTop: 15, backgroundColor: '#fff', }, - contentContainer: { - height: SCREEN_HEIGHT, - paddingTop: '2%', - paddingBottom: SCREEN_HEIGHT / 3, - paddingHorizontal: '3%', - }, - header: { - marginVertical: 20, - zIndex: 1, - }, - recentsHeaderContainer: { - flexDirection: 'row', - }, - recentsHeader: { - fontSize: 17, - fontWeight: 'bold', - flexGrow: 1, - }, - clear: { - fontSize: normalize(17), - fontWeight: 'bold', - color: TAGG_LIGHT_BLUE, - }, - image: { - width: SCREEN_WIDTH, - height: SCREEN_WIDTH, - }, - textContainer: { - marginTop: '10%', - }, - headerText: { - color: '#fff', - fontSize: normalize(32), - fontWeight: '600', - textAlign: 'center', - marginBottom: '4%', - marginHorizontal: '10%', - }, - subtext: { - color: '#fff', - fontSize: normalize(16), - fontWeight: '600', - textAlign: 'center', - marginHorizontal: '10%', - }, - cancelButton: { - position: 'absolute', - height: '100%', - justifyContent: 'center', - paddingHorizontal: 5, - }, - cancelText: { - color: '#818181', - fontWeight: '600', - }, }); export default SearchScreen; diff --git a/src/screens/suggestedPeople/SPBody.tsx b/src/screens/suggestedPeople/SPBody.tsx index 824f8b1c..fa69d812 100644 --- a/src/screens/suggestedPeople/SPBody.tsx +++ b/src/screens/suggestedPeople/SPBody.tsx @@ -3,26 +3,18 @@ import React, {Fragment, useEffect, useMemo, useState} from 'react'; import {StyleSheet, Text, View} from 'react-native'; import {Image} from 'react-native-animatable'; import {TouchableOpacity} from 'react-native-gesture-handler'; -import Animated from 'react-native-reanimated'; -import {useStore} from 'react-redux'; import RequestedButton from '../../assets/ionicons/requested-button.svg'; -import {TaggsBar} from '../../components'; +import {SPTaggsBar} from '../../components'; import {BadgesDropdown, MutualFriends} from '../../components/suggestedPeople'; import {BADGE_DATA} from '../../constants/badges'; -import {RootState} from '../../store/rootReducer'; import { ProfilePreviewType, ScreenType, SuggestedPeopleDataType, UniversityBadge, } from '../../types'; -import { - canViewProfile, - isIPhoneX, - normalize, - SCREEN_HEIGHT, - SCREEN_WIDTH, -} from '../../utils'; +import {isIPhoneX, normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; +import {useSharedValue} from 'react-native-reanimated'; interface SPBodyProps { item: SuggestedPeopleDataType; @@ -56,7 +48,6 @@ const SPBody: React.FC<SPBodyProps> = ({ }[] >([]); const navigation = useNavigation(); - const state: RootState = useStore().getState(); useEffect(() => { const newBadges: {badge: UniversityBadge; img: any}[] = []; const findBadgeIcons = (badge: UniversityBadge) => { @@ -159,12 +150,9 @@ const SPBody: React.FC<SPBodyProps> = ({ {user.id !== loggedInUserId && <FriendButton />} </View> </View> - <TaggsBar - y={Animated.useValue(0)} + <SPTaggsBar userXId={user.id === loggedInUserId ? undefined : user.id} - profileBodyHeight={0} screenType={screenType} - whiteRing={true} linkedSocials={social_links} /> <View style={styles.marginManager}> diff --git a/src/screens/suggestedPeople/SuggestedPeopleScreen.tsx b/src/screens/suggestedPeople/SuggestedPeopleScreen.tsx index a296351f..d6812f41 100644 --- a/src/screens/suggestedPeople/SuggestedPeopleScreen.tsx +++ b/src/screens/suggestedPeople/SuggestedPeopleScreen.tsx @@ -226,7 +226,7 @@ const SuggestedPeopleScreen: React.FC = () => { /> ); }} - keyExtractor={(item, index) => index.toString()} + keyExtractor={(_, index) => index.toString()} showsVerticalScrollIndicator={false} onViewableItemsChanged={onViewableItemsChanged} onEndReached={() => setPage(page + 1)} |
