From 5cf616a6234b5f002318415e8e6f9ed73e10368d Mon Sep 17 00:00:00 2001 From: ankit-thanekar007 Date: Wed, 31 Mar 2021 14:08:03 -0700 Subject: Changes for empty contacts and text getting cut --- src/components/common/TaggPrompt.tsx | 2 +- src/components/profile/Friends.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/components') diff --git a/src/components/common/TaggPrompt.tsx b/src/components/common/TaggPrompt.tsx index 721b1eb8..5e125d00 100644 --- a/src/components/common/TaggPrompt.tsx +++ b/src/components/common/TaggPrompt.tsx @@ -68,7 +68,7 @@ const styles = StyleSheet.create({ justifyContent: 'center', alignItems: 'center', backgroundColor: 'white', - height: isIPhoneX() ? SCREEN_HEIGHT / 6 : SCREEN_HEIGHT / 5, + height: SCREEN_HEIGHT / 4, }, closeButton: { position: 'relative', diff --git a/src/components/profile/Friends.tsx b/src/components/profile/Friends.tsx index 44f6bb48..b9ca4d7b 100644 --- a/src/components/profile/Friends.tsx +++ b/src/components/profile/Friends.tsx @@ -39,7 +39,7 @@ const Friends: React.FC = ({result, screenType, userId}) => { const permission = await checkPermission(); if (permission === 'authorized') { let response = await usersFromContactsService(contacts); - await setUsersFromContacts(response.existing_tagg_users); + setUsersFromContacts(response.existing_tagg_users); } else { console.log('Authorize access to contacts'); } @@ -84,7 +84,7 @@ const Friends: React.FC = ({result, screenType, userId}) => { return ( <> - {loggedInUser.userId === userId && ( + {loggedInUser.userId === userId && usersFromContacts.length !== 0 && ( Contacts on tagg -- cgit v1.2.3-70-g09d2 From f3a9e82020549b04834de6ac5a0ce1096f01e3f2 Mon Sep 17 00:00:00 2001 From: ankit-thanekar007 Date: Wed, 31 Mar 2021 14:33:40 -0700 Subject: tagg --> Tagg --- src/components/profile/Friends.tsx | 4 ++-- src/screens/profile/InviteFriendsScreen.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/components') diff --git a/src/components/profile/Friends.tsx b/src/components/profile/Friends.tsx index b9ca4d7b..c1dca755 100644 --- a/src/components/profile/Friends.tsx +++ b/src/components/profile/Friends.tsx @@ -27,7 +27,7 @@ interface FriendsProps { const Friends: React.FC = ({result, screenType, userId}) => { const state: RootState = useStore().getState(); const dispatch = useDispatch(); - const {user: loggedInUser = NO_USER} = state; + const {user: loggedInUser = NO_USER} = state.user; const navigation = useNavigation(); const [usersFromContacts, setUsersFromContacts] = useState< ProfilePreviewType[] @@ -87,7 +87,7 @@ const Friends: React.FC = ({result, screenType, userId}) => { {loggedInUser.userId === userId && usersFromContacts.length !== 0 && ( - Contacts on tagg + Contacts on Tagg 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 = ({route}) => { - Contacts on tagg + Contacts on Tagg -- cgit v1.2.3-70-g09d2 From ecd44e517cbb978c0a590a5861cb2cd357d0fc11 Mon Sep 17 00:00:00 2001 From: Leon Jiang <35908040+leonyjiang@users.noreply.github.com> Date: Thu, 1 Apr 2021 14:19:17 -0400 Subject: Add chat to navigation --- src/components/common/NavigationIcon.tsx | 10 +++++++++- src/routes/main/MainStackNavigator.tsx | 2 ++ src/routes/main/MainStackScreen.tsx | 14 ++++++++++++++ src/routes/tabs/NavigationBar.tsx | 7 +++++++ src/types/types.ts | 1 + 5 files changed, 33 insertions(+), 1 deletion(-) (limited to 'src/components') diff --git a/src/components/common/NavigationIcon.tsx b/src/components/common/NavigationIcon.tsx index 1a9934f2..5128f3da 100644 --- a/src/components/common/NavigationIcon.tsx +++ b/src/components/common/NavigationIcon.tsx @@ -14,7 +14,8 @@ interface NavigationIconProps extends TouchableOpacityProps { | 'Upload' | 'Notifications' | 'Profile' - | 'SuggestedPeople'; + | 'SuggestedPeople' + | 'Chat'; disabled?: boolean; newIcon?: boolean; } @@ -44,6 +45,13 @@ const NavigationIcon = (props: NavigationIconProps) => { : require('../../assets/navigationIcons/notifications.png') : require('../../assets/navigationIcons/notifications-clicked.png'); break; + case 'Chat': + imgSrc = props.disabled + ? props.newIcon + ? require('../../assets/navigationIcons/chat-notifications.png') + : require('../../assets/navigationIcons/chat.png') + : require('../../assets/navigationIcons/chat-clicked.png'); + break; case 'Profile': imgSrc = props.disabled ? require('../../assets/navigationIcons/profile.png') diff --git a/src/routes/main/MainStackNavigator.tsx b/src/routes/main/MainStackNavigator.tsx index 9b089634..021c0688 100644 --- a/src/routes/main/MainStackNavigator.tsx +++ b/src/routes/main/MainStackNavigator.tsx @@ -92,6 +92,8 @@ export type MainStackParams = { screenType: ScreenType; }; SPWelcomeScreen: {}; + ChatList: undefined; + Chat: undefined; }; export const MainStack = createStackNavigator(); diff --git a/src/routes/main/MainStackScreen.tsx b/src/routes/main/MainStackScreen.tsx index d855f0df..8068b893 100644 --- a/src/routes/main/MainStackScreen.tsx +++ b/src/routes/main/MainStackScreen.tsx @@ -29,6 +29,8 @@ import { SuggestedPeopleUploadPictureScreen, SuggestedPeopleWelcomeScreen, SettingsScreen, + ChatListScreen, + ChatScreen, } from '../../screens'; import MutualBadgeHolders from '../../screens/suggestedPeople/MutualBadgeHolders'; import {ScreenType} from '../../types'; @@ -85,6 +87,8 @@ const MainStackScreen: React.FC = ({route}) => { return 'Notifications'; case ScreenType.SuggestedPeople: return 'SuggestedPeople'; + case ScreenType.Chat: + return 'ChatList'; } })(); @@ -294,6 +298,16 @@ const MainStackScreen: React.FC = ({route}) => { ...headerBarOptions('white', ''), }} /> + + ); }; diff --git a/src/routes/tabs/NavigationBar.tsx b/src/routes/tabs/NavigationBar.tsx index e9208525..9b8427e7 100644 --- a/src/routes/tabs/NavigationBar.tsx +++ b/src/routes/tabs/NavigationBar.tsx @@ -54,6 +54,8 @@ const NavigationBar: React.FC = () => { disabled={!focused} /> ); + case 'Chat': + return ; case 'Profile': return ; case 'SuggestedPeople': @@ -92,6 +94,11 @@ const NavigationBar: React.FC = () => { component={MainStackScreen} initialParams={{screenType: ScreenType.Notifications}} /> + Date: Thu, 1 Apr 2021 23:58:02 -0400 Subject: Improve position of overlaid tutorial --- src/components/profile/Content.tsx | 8 ++++++++ src/components/profile/PublicProfile.tsx | 3 +++ src/components/taggs/TaggsBar.tsx | 7 +++++-- src/routes/main/MainStackNavigator.tsx | 1 + src/screens/profile/MomentUploadPromptScreen.tsx | 20 ++++++++++++-------- src/screens/profile/ProfileScreen.tsx | 8 ++------ src/types/types.ts | 1 + 7 files changed, 32 insertions(+), 16 deletions(-) (limited to 'src/components') diff --git a/src/components/profile/Content.tsx b/src/components/profile/Content.tsx index 9c33eabc..fef92dc1 100644 --- a/src/components/profile/Content.tsx +++ b/src/components/profile/Content.tsx @@ -66,6 +66,7 @@ const Content: React.FC = ({y, userXId, screenType}) => { */ const [isBlocked, setIsBlocked] = useState(false); const [profileBodyHeight, setProfileBodyHeight] = useState(0); + const [socialsBarHeight, setSocialsBarHeight] = useState(0); const [shouldBounce, setShouldBounce] = useState(true); const [refreshing, setRefreshing] = useState(false); @@ -88,6 +89,11 @@ const Content: React.FC = ({y, userXId, screenType}) => { setProfileBodyHeight(height); }; + const onSocialsBarLayout = (e: LayoutChangeEvent) => { + const {height} = e.nativeEvent.layout; + setSocialsBarHeight(height); + }; + useEffect(() => { const isActuallyBlocked = blockedUsers.some( (cur_user) => user.username === cur_user.username, @@ -166,6 +172,7 @@ const Content: React.FC = ({y, userXId, screenType}) => { {canViewProfile(state, userXId, screenType) ? ( = ({y, userXId, screenType}) => { screenType, setScrollEnabled, profileBodyHeight, + socialsBarHeight, scrollViewRef, }} /> diff --git a/src/components/profile/PublicProfile.tsx b/src/components/profile/PublicProfile.tsx index 88e0ecd1..eceb2fc3 100644 --- a/src/components/profile/PublicProfile.tsx +++ b/src/components/profile/PublicProfile.tsx @@ -35,6 +35,7 @@ const PublicProfile: React.FC = ({ screenType, setScrollEnabled, profileBodyHeight, + socialsBarHeight, scrollViewRef, }) => { const dispatch = useDispatch(); @@ -104,6 +105,7 @@ const PublicProfile: React.FC = ({ screenType, momentCategory: momentCategories[0], profileBodyHeight, + socialsBarHeight, }); setIsStageOnePromptClosed(true); } @@ -133,6 +135,7 @@ const PublicProfile: React.FC = ({ navigation, screenType, profileBodyHeight, + socialsBarHeight, scrollViewRef, ]), ); diff --git a/src/components/taggs/TaggsBar.tsx b/src/components/taggs/TaggsBar.tsx index 567b58de..ec91b8e5 100644 --- a/src/components/taggs/TaggsBar.tsx +++ b/src/components/taggs/TaggsBar.tsx @@ -1,5 +1,5 @@ import React, {Fragment, useEffect, useState} from 'react'; -import {StyleSheet} from 'react-native'; +import {StyleSheet, LayoutChangeEvent} from 'react-native'; import Animated from 'react-native-reanimated'; import {useSafeAreaInsets} from 'react-native-safe-area-context'; import {useDispatch, useSelector, useStore} from 'react-redux'; @@ -23,6 +23,7 @@ interface TaggsBarProps { screenType: ScreenType; whiteRing: boolean | undefined; linkedSocials?: string[]; + onLayout: (event: LayoutChangeEvent) => void; } const TaggsBar: React.FC = ({ y, @@ -31,6 +32,7 @@ const TaggsBar: React.FC = ({ screenType, whiteRing, linkedSocials, + onLayout, }) => { let [taggs, setTaggs] = useState([]); let [taggsNeedUpdate, setTaggsNeedUpdate] = useState(true); @@ -138,7 +140,8 @@ const TaggsBar: React.FC = ({ whiteRing ? [styles.spContainer] : [styles.container, {shadowOpacity, paddingTop}] - }> + } + onLayout={onLayout}> = ({ route, navigation, }) => { - const {screenType, momentCategory, profileBodyHeight} = route.params; + const { + screenType, + momentCategory, + profileBodyHeight, + socialsBarHeight, + } = route.params; return ( = ({ 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..0d6af21e 100644 --- a/src/screens/profile/ProfileScreen.tsx +++ b/src/screens/profile/ProfileScreen.tsx @@ -1,12 +1,9 @@ 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 @@ -23,7 +20,6 @@ const ProfileScreen: React.FC = ({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. diff --git a/src/types/types.ts b/src/types/types.ts index 766bf798..bb83e839 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -214,6 +214,7 @@ export interface ContentProps { screenType: ScreenType; setScrollEnabled: (enabled: boolean) => void; profileBodyHeight: number; + socialsBarHeight: number; scrollViewRef: React.MutableRefObject; } -- cgit v1.2.3-70-g09d2 From 4edcaf03ae6e41ba9eb11a381540db4692375463 Mon Sep 17 00:00:00 2001 From: Ivan Chen Date: Fri, 2 Apr 2021 16:06:20 -0400 Subject: added initial styling --- src/components/index.ts | 1 + src/components/messages/MessagesHeader.tsx | 51 ++++++++++++++++++++++++++++ src/components/messages/index.ts | 1 + src/screens/chat/ChatListScreen.tsx | 54 ++++++++++++------------------ 4 files changed, 75 insertions(+), 32 deletions(-) create mode 100644 src/components/messages/MessagesHeader.tsx create mode 100644 src/components/messages/index.ts (limited to 'src/components') diff --git a/src/components/index.ts b/src/components/index.ts index d5649323..47dc583b 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -6,3 +6,4 @@ export * from './taggs'; export * from './comments'; export * from './moments'; export * from './suggestedPeople'; +export * from './messages'; diff --git a/src/components/messages/MessagesHeader.tsx b/src/components/messages/MessagesHeader.tsx new file mode 100644 index 00000000..47a84bf3 --- /dev/null +++ b/src/components/messages/MessagesHeader.tsx @@ -0,0 +1,51 @@ +import * as React from 'react'; +import {Alert, StyleSheet, View} from 'react-native'; +import {Text} from 'react-native-animatable'; +import {TouchableOpacity} from 'react-native-gesture-handler'; +import {normalize} from '../../utils'; + +type MessagesHeaderProps = {}; + +const MessagesHeader: React.FC = () => { + return ( + + Messages + 2 unread + + { + Alert.alert('hi'); + }}> + Compose + + + ); +}; + +const styles = StyleSheet.create({ + flex: { + flex: 1, + }, + header: { + marginHorizontal: '8%', + marginTop: '5%', + alignItems: 'center', + flexDirection: 'row', + }, + headerText: { + fontWeight: '700', + fontSize: normalize(18), + lineHeight: normalize(21), + }, + unreadText: { + color: '#8F01FF', + marginLeft: 10, + fontWeight: '700', + lineHeight: normalize(17), + fontSize: normalize(14), + }, + compose: {}, +}); + +export default MessagesHeader; diff --git a/src/components/messages/index.ts b/src/components/messages/index.ts new file mode 100644 index 00000000..2d6bb581 --- /dev/null +++ b/src/components/messages/index.ts @@ -0,0 +1 @@ +export {default as MessagesHeader} from './MessagesHeader'; diff --git a/src/screens/chat/ChatListScreen.tsx b/src/screens/chat/ChatListScreen.tsx index 416e7936..56493cf7 100644 --- a/src/screens/chat/ChatListScreen.tsx +++ b/src/screens/chat/ChatListScreen.tsx @@ -1,21 +1,9 @@ -import {StreamChat} from 'stream-chat'; -import React, {useEffect, useMemo, useState} from 'react'; -import {View, StyleSheet, Text, TouchableOpacity} from 'react-native'; import {StackNavigationProp} from '@react-navigation/stack'; -import { - Channel, - ChannelList, - Chat, - MessageInput, - MessageList, - OverlayProvider, - Streami18n, - Thread, - ThreadContextValue, - useAttachmentPickerContext, - useOverlayContext, -} from 'stream-chat-react-native'; - +import React, {useEffect, useMemo, useState} from 'react'; +import {SafeAreaView, StatusBar, StyleSheet, View} from 'react-native'; +import {StreamChat} from 'stream-chat'; +import {ChannelList, Chat, Streami18n} from 'stream-chat-react-native'; +import {MessagesHeader} from '../../components/messages'; import {MainStackParams} from '../../routes'; type ChatListScreenNavigationProp = StackNavigationProp< @@ -60,23 +48,17 @@ const ChatListScreen: React.FC = ({navigation}) => { const memoizedFilters = useMemo(() => filters, []); return ( - - I am the chat list. - navigation.navigate('Chat')}> - Let's go to a conversation! - - {clientReady && ( - <> - Fooooo + + + + + {clientReady && ( - + { - // setChannel(channel); - // navigation.navigate('Channel'); + console.log('Navigate to chat screen here'); }} options={{ presence: true, @@ -87,13 +69,17 @@ const ChatListScreen: React.FC = ({navigation}) => { /> - - )} + )} + ); }; const styles = StyleSheet.create({ + background: { + flex: 1, + backgroundColor: 'white', + }, container: { flex: 1, justifyContent: 'center', @@ -109,6 +95,10 @@ const styles = StyleSheet.create({ padding: 15, borderRadius: 5, }, + chatContainer: { + height: '100%', + marginTop: 10, + }, }); export default ChatListScreen; -- cgit v1.2.3-70-g09d2 From c03eba730ad99bbadc49601f5f9387c1ca4c0eac Mon Sep 17 00:00:00 2001 From: Ivan Chen Date: Fri, 2 Apr 2021 17:00:11 -0400 Subject: created navigation to chat --- src/components/messages/MessagesHeader.tsx | 10 ++++--- src/routes/main/MainStackNavigator.tsx | 2 +- src/screens/chat/ChatListScreen.tsx | 44 +++++++++++++++++++++++------- src/screens/chat/ChatScreen.tsx | 9 +++++- 4 files changed, 49 insertions(+), 16 deletions(-) (limited to 'src/components') diff --git a/src/components/messages/MessagesHeader.tsx b/src/components/messages/MessagesHeader.tsx index 47a84bf3..3b8144f7 100644 --- a/src/components/messages/MessagesHeader.tsx +++ b/src/components/messages/MessagesHeader.tsx @@ -1,12 +1,14 @@ import * as React from 'react'; -import {Alert, StyleSheet, View} from 'react-native'; +import {StyleSheet, View} from 'react-native'; import {Text} from 'react-native-animatable'; import {TouchableOpacity} from 'react-native-gesture-handler'; import {normalize} from '../../utils'; -type MessagesHeaderProps = {}; +type MessagesHeaderProps = { + createChannel: () => void; +}; -const MessagesHeader: React.FC = () => { +const MessagesHeader: React.FC = ({createChannel}) => { return ( Messages @@ -15,7 +17,7 @@ const MessagesHeader: React.FC = () => { { - Alert.alert('hi'); + createChannel(); }}> Compose diff --git a/src/routes/main/MainStackNavigator.tsx b/src/routes/main/MainStackNavigator.tsx index 021c0688..36c6fb57 100644 --- a/src/routes/main/MainStackNavigator.tsx +++ b/src/routes/main/MainStackNavigator.tsx @@ -93,7 +93,7 @@ export type MainStackParams = { }; SPWelcomeScreen: {}; ChatList: undefined; - Chat: undefined; + Chat: {channel: any; chatClient: any}; }; export const MainStack = createStackNavigator(); diff --git a/src/screens/chat/ChatListScreen.tsx b/src/screens/chat/ChatListScreen.tsx index 56493cf7..106995e0 100644 --- a/src/screens/chat/ChatListScreen.tsx +++ b/src/screens/chat/ChatListScreen.tsx @@ -19,26 +19,37 @@ interface ChatListScreenProps { const ChatListScreen: React.FC = ({navigation}) => { const filters = { example: 'example-apps', - members: {$in: ['ron']}, + members: {$in: ['john']}, type: 'messaging', }; const [clientReady, setClientReady] = useState(false); - const chatClient = StreamChat.getInstance('q95x9hkbyd6p'); - const userToken = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicm9uIn0.eRVjxLvd4aqCEHY_JRa97g6k7WpHEhxL7Z4K4yTot1c'; - const user = { - id: 'ron', - }; + const chatClient = StreamChat.getInstance('t823wwqn2wuk'); + // const chatClient = StreamChat.getInstance('q95x9hkbyd6p'); useEffect(() => { const setupClient = async () => { - await chatClient.connectUser(user, userToken); + // await chatClient.connectUser( + // { + // id: 'ron', + // }, + // 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicm9uIn0.eRVjxLvd4aqCEHY_JRa97g6k7WpHEhxL7Z4K4yTot1c', + // ); + await chatClient.connectUser( + { + id: 'john', + name: 'John Doe', + image: 'https://getstream.io/random_svg/?name=John', + }, + chatClient.devToken('john'), + ); return setClientReady(true); }; - setupClient(); + setupClient().catch((err) => { + console.error(err); + }); }, []); const streami18n = new Streami18n({ @@ -51,7 +62,16 @@ const ChatListScreen: React.FC = ({navigation}) => { - + { + const channel = chatClient.channel('messaging', 'travel2', { + name: 'Awesome channel about traveling', + members: ['john'], + }); + // console.log(JSON.stringify(channel)); + channel.create(); + }} + /> {clientReady && ( @@ -59,6 +79,10 @@ const ChatListScreen: React.FC = ({navigation}) => { filters={memoizedFilters} onSelect={(channel) => { console.log('Navigate to chat screen here'); + navigation.navigate('Chat', { + channel, + chatClient, + }); }} options={{ presence: true, diff --git a/src/screens/chat/ChatScreen.tsx b/src/screens/chat/ChatScreen.tsx index af83f504..08145b89 100644 --- a/src/screens/chat/ChatScreen.tsx +++ b/src/screens/chat/ChatScreen.tsx @@ -3,15 +3,22 @@ import {View, StyleSheet, Text} from 'react-native'; import {StackNavigationProp} from '@react-navigation/stack'; import {MainStackParams} from '../../routes'; +import {navigationRef} from '../../RootNavigation'; +import {RouteProp} from '@react-navigation/native'; type ChatScreenNavigationProp = StackNavigationProp; +type ChatScreenRouteProp = RouteProp; interface ChatScreenProps { navigation: ChatScreenNavigationProp; + route: ChatScreenRouteProp; } /* * Screen that displays all of the user's active conversations. */ -const ChatScreen: React.FC = () => { +const ChatScreen: React.FC = ({route}) => { + const {channel, chatClient} = route.params; + console.log(channel); + console.log(chatClient); return ( I am a chat! -- cgit v1.2.3-70-g09d2 From 7b061c35948609a1f7f3ef3cef8b5e5be964d4c2 Mon Sep 17 00:00:00 2001 From: Ivan Chen Date: Fri, 2 Apr 2021 19:01:22 -0400 Subject: commented out all the old reanimated stuff, added TODOs for leon --- src/components/common/BottomDrawer.tsx | 13 +++---- src/components/search/SearchBar.tsx | 20 ++++++----- src/components/search/SearchResultsBackground.tsx | 20 ++++++----- src/components/taggs/TaggsBar.tsx | 41 ++++++++++++----------- src/routes/main/MainStackScreen.tsx | 18 +++++----- 5 files changed, 63 insertions(+), 49 deletions(-) (limited to 'src/components') diff --git a/src/components/common/BottomDrawer.tsx b/src/components/common/BottomDrawer.tsx index bef9434a..173823b1 100644 --- a/src/components/common/BottomDrawer.tsx +++ b/src/components/common/BottomDrawer.tsx @@ -78,12 +78,13 @@ const BottomDrawer: React.FC = (props) => { diff --git a/src/components/search/SearchBar.tsx b/src/components/search/SearchBar.tsx index 4824b56f..7b833406 100644 --- a/src/components/search/SearchBar.tsx +++ b/src/components/search/SearchBar.tsx @@ -109,14 +109,18 @@ const SearchBar: React.FC = ({ /* * Animated nodes used in search bar activation animation. */ - const marginRight: Animated.Node = interpolate(top, { - inputRange: [-SCREEN_HEIGHT, 0], - outputRange: [0, 58], - }); - const opacity: Animated.Node = interpolate(top, { - inputRange: [-SCREEN_HEIGHT, 0], - outputRange: [0, 1], - }); + // TODO: (Leon) use reanimated v2 + const marginRight = 0; + // const marginRight: Animated.Node = interpolate(top, { + // inputRange: [-SCREEN_HEIGHT, 0], + // outputRange: [0, 58], + // }); + // TODO: (Leon) use reanimated v2 + const opacity = 0; + // const opacity: Animated.Node = interpolate(top, { + // inputRange: [-SCREEN_HEIGHT, 0], + // outputRange: [0, 1], + // }); return ( diff --git a/src/components/search/SearchResultsBackground.tsx b/src/components/search/SearchResultsBackground.tsx index 2833553d..3d7fab4e 100644 --- a/src/components/search/SearchResultsBackground.tsx +++ b/src/components/search/SearchResultsBackground.tsx @@ -10,14 +10,18 @@ const SearchResultsBackground: React.FC = ({ top, children, }) => { - const opacityBackground: Animated.Node = interpolate(top, { - inputRange: [-SCREEN_HEIGHT, 0], - outputRange: [0, 1], - }); - const opacityContent: Animated.Node = interpolate(top, { - inputRange: [-SCREEN_HEIGHT / 40, 0], - outputRange: [0, 1], - }); + // TODO: (Leon) use reanimated v2 + const opacityBackground = 0; + // const opacityBackground: Animated.Node = interpolate(top, { + // inputRange: [-SCREEN_HEIGHT, 0], + // outputRange: [0, 1], + // }); + // TODO: (Leon) use reanimated v2 + const opacityContent = 0; + // const opacityContent: Animated.Node = interpolate(top, { + // inputRange: [-SCREEN_HEIGHT / 40, 0], + // outputRange: [0, 1], + // }); return ( diff --git a/src/components/taggs/TaggsBar.tsx b/src/components/taggs/TaggsBar.tsx index 567b58de..a87da3e2 100644 --- a/src/components/taggs/TaggsBar.tsx +++ b/src/components/taggs/TaggsBar.tsx @@ -112,25 +112,28 @@ const TaggsBar: React.FC = ({ loadData(); } }, [taggsNeedUpdate, user]); - - const shadowOpacity: Animated.Node = interpolate(y, { - inputRange: [ - PROFILE_CUTOUT_BOTTOM_Y + profileBodyHeight, - PROFILE_CUTOUT_BOTTOM_Y + profileBodyHeight + 20, - ], - outputRange: [0, 0.2], - extrapolate: Extrapolate.CLAMP, - }); - const paddingTop: Animated.Node = interpolate(y, { - inputRange: [ - PROFILE_CUTOUT_BOTTOM_Y + - profileBodyHeight - - (useSafeAreaInsets().top + 10), - PROFILE_CUTOUT_BOTTOM_Y + profileBodyHeight, - ], - outputRange: [10, useSafeAreaInsets().top], - extrapolate: Extrapolate.CLAMP, - }); +// TODO: (Leon) use reanimated v2 + const shadowOpacity = 0; + // const shadowOpacity: Animated.Node = interpolate(y, { + // inputRange: [ + // PROFILE_CUTOUT_BOTTOM_Y + profileBodyHeight, + // PROFILE_CUTOUT_BOTTOM_Y + profileBodyHeight + 20, + // ], + // outputRange: [0, 0.2], + // extrapolate: Extrapolate.CLAMP, + // }); + // TODO: (Leon) use reanimated v2 + const paddingTop = 0; + // const paddingTop: Animated.Node = interpolate(y, { + // inputRange: [ + // PROFILE_CUTOUT_BOTTOM_Y + + // profileBodyHeight - + // (useSafeAreaInsets().top + 10), + // PROFILE_CUTOUT_BOTTOM_Y + profileBodyHeight, + // ], + // outputRange: [10, useSafeAreaInsets().top], + // extrapolate: Extrapolate.CLAMP, + // }); return taggs.length > 0 ? ( = ({route}) => { cardOverlayEnabled: true, cardStyleInterpolator: ({current: {progress}}) => ({ cardStyle: { - opacity: progress.interpolate({ - inputRange: [0, 0.5, 0.9, 1], - outputRange: [0, 0.25, 0.7, 1], - }), + // TODO: (Leon) use reanimated v2 + // opacity: progress.interpolate({ + // inputRange: [0, 0.5, 0.9, 1], + // outputRange: [0, 0.25, 0.7, 1], + // }), }, }), }; @@ -348,10 +349,11 @@ export const modalStyle: StackNavigationOptions = { cardOverlayEnabled: true, cardStyleInterpolator: ({current: {progress}}) => ({ cardStyle: { - opacity: progress.interpolate({ - inputRange: [0, 0.5, 0.9, 1], - outputRange: [0, 0.25, 0.7, 1], - }), + // TODO: (Leon) use reanimated v2 + // opacity: progress.interpolate({ + // inputRange: [0, 0.5, 0.9, 1], + // outputRange: [0, 0.25, 0.7, 1], + // }), }, }), }; -- cgit v1.2.3-70-g09d2 From 268c93e705e3d3808ab5353497354f390230d29d Mon Sep 17 00:00:00 2001 From: Leon Jiang <35908040+leonyjiang@users.noreply.github.com> Date: Sat, 3 Apr 2021 15:29:56 -0400 Subject: Fix search bar animation & search screen styles --- src/components/common/GradientBorderButton.tsx | 2 +- src/components/common/TabsGradient.tsx | 2 +- src/components/profile/PublicProfile.tsx | 2 +- src/components/search/RecentSearches.tsx | 31 ++-- src/components/search/SearchBar.tsx | 37 ++--- src/components/search/SearchCategories.tsx | 68 ++++---- src/components/search/SearchResultList.tsx | 85 +++++----- src/components/search/SearchResults.tsx | 14 +- src/components/search/SearchResultsBackground.tsx | 69 ++++++--- src/screens/search/DiscoverUsers.tsx | 2 +- src/screens/search/SearchScreen.tsx | 180 +++++++--------------- 11 files changed, 211 insertions(+), 281 deletions(-) (limited to 'src/components') diff --git a/src/components/common/GradientBorderButton.tsx b/src/components/common/GradientBorderButton.tsx index 32ac5c52..a5dbde9d 100644 --- a/src/components/common/GradientBorderButton.tsx +++ b/src/components/common/GradientBorderButton.tsx @@ -42,7 +42,7 @@ const GradientBorderButton: React.FC = ({ }; const styles = StyleSheet.create({ container: { - marginVertical: 15, + marginVertical: 10, }, gradientContainer: { width: SCREEN_WIDTH / 2 - 40, diff --git a/src/components/common/TabsGradient.tsx b/src/components/common/TabsGradient.tsx index a95e8bc3..07c55042 100644 --- a/src/components/common/TabsGradient.tsx +++ b/src/components/common/TabsGradient.tsx @@ -14,7 +14,7 @@ const TabsGradient: React.FC = () => { }; const styles = StyleSheet.create({ gradient: { - position: 'absolute', + ...StyleSheet.absoluteFillObject, top: (SCREEN_HEIGHT / 10) * 9, height: SCREEN_HEIGHT / 10, width: SCREEN_WIDTH, diff --git a/src/components/profile/PublicProfile.tsx b/src/components/profile/PublicProfile.tsx index 88e0ecd1..5f9b0b99 100644 --- a/src/components/profile/PublicProfile.tsx +++ b/src/components/profile/PublicProfile.tsx @@ -99,7 +99,7 @@ const PublicProfile: React.FC = ({ scrollViewRef.current ) { setScrollEnabled(false); - scrollViewRef.current.getNode().scrollTo({y: 0}); + scrollViewRef.current.scrollTo({y: 0}); navigation.navigate('MomentUploadPrompt', { screenType, momentCategory: momentCategories[0], diff --git a/src/components/search/RecentSearches.tsx b/src/components/search/RecentSearches.tsx index 84d35cac..6cea9338 100644 --- a/src/components/search/RecentSearches.tsx +++ b/src/components/search/RecentSearches.tsx @@ -6,6 +6,7 @@ import { StyleSheet, TouchableOpacityProps, ScrollView, + Keyboard, } from 'react-native'; import { PreviewType, @@ -15,7 +16,7 @@ import { } from '../../types'; import {TAGG_LIGHT_BLUE} from '../../constants'; import SearchResults from './SearchResults'; -import {SCREEN_HEIGHT} from '../../utils'; +import {useBottomTabBarHeight} from '@react-navigation/bottom-tabs'; interface RecentSearchesProps extends TouchableOpacityProps { sectionTitle: PreviewType; @@ -25,35 +26,25 @@ interface RecentSearchesProps extends TouchableOpacityProps { } const RecentSearches: React.FC = (props) => { - const {sectionTitle, recents, recentCategories, screenType} = props; + const {recents, recentCategories} = props; return ( - + <> - {sectionTitle} + Recent Clear all - - + + + + ); }; const styles = StyleSheet.create({ - mainContainer: { - flex: 1, - }, - contentContainer: { - paddingBottom: SCREEN_HEIGHT * 0.1, - flex: 1, - }, header: { paddingHorizontal: 25, paddingVertical: 5, diff --git a/src/components/search/SearchBar.tsx b/src/components/search/SearchBar.tsx index 7b833406..d441b07b 100644 --- a/src/components/search/SearchBar.tsx +++ b/src/components/search/SearchBar.tsx @@ -9,20 +9,23 @@ import { TextInputSubmitEditingEventData, TouchableOpacity, View, + ViewStyle, + LayoutChangeEvent, } from 'react-native'; import {normalize} from 'react-native-elements'; -import Animated, {interpolate} from 'react-native-reanimated'; +import Animated, {useAnimatedStyle} from 'react-native-reanimated'; import Icon from 'react-native-vector-icons/Feather'; import {useSelector} from 'react-redux'; import {RootState} from '../../store/rootReducer'; -import {getSearchSuggestions, SCREEN_HEIGHT} from '../../utils'; +import {getSearchSuggestions} from '../../utils'; const AnimatedIcon = Animated.createAnimatedComponent(Icon); interface SearchBarProps extends TextInputProps { onCancel: () => void; - top: Animated.Value; + animationProgress: Animated.SharedValue; searching: boolean; + onLayout: (e: LayoutChangeEvent) => void; } const SearchBar: React.FC = ({ onFocus, @@ -31,7 +34,8 @@ const SearchBar: React.FC = ({ value, onCancel, searching, - top, + animationProgress, + onLayout, }) => { const handleSubmit = ( e: NativeSyntheticEvent, @@ -107,23 +111,15 @@ const SearchBar: React.FC = ({ }, [searching]); /* - * Animated nodes used in search bar activation animation. + * On-search marginRight style ("cancel" button slides and fades in). */ - // TODO: (Leon) use reanimated v2 - const marginRight = 0; - // const marginRight: Animated.Node = interpolate(top, { - // inputRange: [-SCREEN_HEIGHT, 0], - // outputRange: [0, 58], - // }); - // TODO: (Leon) use reanimated v2 - const opacity = 0; - // const opacity: Animated.Node = interpolate(top, { - // inputRange: [-SCREEN_HEIGHT, 0], - // outputRange: [0, 1], - // }); + const animatedStyles = useAnimatedStyle(() => ({ + marginRight: animationProgress.value * 58, + opacity: animationProgress.value, + })); return ( - + = ({ style={[styles.input]} placeholderTextColor={'#828282'} onSubmitEditing={handleSubmit} - clearButtonMode="while-editing" + clearButtonMode="always" autoCapitalize="none" autoCorrect={false} {...{placeholder, value, onChangeText, onFocus, onBlur}} /> - + Cancel @@ -155,6 +151,7 @@ const styles = StyleSheet.create({ height: 40, paddingHorizontal: 20, flexDirection: 'row', + zIndex: 2, }, inputContainer: { flexGrow: 1, diff --git a/src/components/search/SearchCategories.tsx b/src/components/search/SearchCategories.tsx index c747b34f..3d142981 100644 --- a/src/components/search/SearchCategories.tsx +++ b/src/components/search/SearchCategories.tsx @@ -3,29 +3,40 @@ import React, {useEffect, useState} from 'react'; import {StyleSheet, View} from 'react-native'; import {getSuggestedSearchBubbleSuggestions} from '../../services/ExploreService'; import {SearchCategoryType} from '../../types'; -import {SCREEN_WIDTH} from '../../utils'; import GradientBorderButton from '../common/GradientBorderButton'; +import {useSelector} from 'react-redux'; +import {RootState} from 'src/store/rootReducer'; interface SearchCategoriesProps { darkStyle?: boolean; - defaultButtons?: SearchCategoryType[]; + useSuggestions: boolean; } const SearchCategories: React.FC = ({ darkStyle = false, - defaultButtons, + useSuggestions, }) => { const navigation = useNavigation(); - const mtSearchCategory: (key: number) => SearchCategoryType = (key) => ({ + const { + profile: {university = ''}, + } = useSelector((state: RootState) => state.user); + const defaultButtons: SearchCategoryType[] = [21, 22, 23, 24].map( + (year, index) => ({ + id: index * -1, + name: `${university.split(' ')[0]} '${year}`, + category: university, + }), + ); + const createloadingCategory: (key: number) => SearchCategoryType = (key) => ({ id: key, name: '...', category: '...', }); const [buttons, setButtons] = useState([ - mtSearchCategory(-1), - mtSearchCategory(-2), - mtSearchCategory(-3), - mtSearchCategory(-4), + createloadingCategory(1), + createloadingCategory(2), + createloadingCategory(3), + createloadingCategory(4), ]); useEffect(() => { @@ -36,7 +47,7 @@ const SearchCategories: React.FC = ({ setButtons(localButtons); } }; - if (!defaultButtons) { + if (useSuggestions) { loadButtons(); } else { setButtons(defaultButtons); @@ -45,33 +56,34 @@ const SearchCategories: React.FC = ({ return ( - {buttons.map((searchCategory) => ( - { - if (searchCategory.name !== '...') { - navigation.push('DiscoverUsers', { - searchCategory, - }); - } - }} - /> - ))} + + {buttons.map((searchCategory, index) => ( + { + if (searchCategory.name !== '...') { + navigation.push('DiscoverUsers', { + searchCategory, + }); + } + }} + /> + ))} + ); }; const styles = StyleSheet.create({ container: { - zIndex: 0, - top: '3%', - alignSelf: 'center', + paddingVertical: 20, + }, + categoryContainer: { flexDirection: 'row', - width: SCREEN_WIDTH * 0.9, - flexWrap: 'wrap', justifyContent: 'space-evenly', + flexWrap: 'wrap', }, }); export default SearchCategories; diff --git a/src/components/search/SearchResultList.tsx b/src/components/search/SearchResultList.tsx index 687b2285..a32760e1 100644 --- a/src/components/search/SearchResultList.tsx +++ b/src/components/search/SearchResultList.tsx @@ -1,15 +1,23 @@ import React, {useEffect, useState} from 'react'; -import {SectionList, StyleSheet, Text, View} from 'react-native'; +import { + SectionList, + StyleSheet, + Text, + View, + Keyboard, + SectionListData, +} from 'react-native'; import {useSelector} from 'react-redux'; import {RootState} from '../../store/rootreducer'; import {NO_RESULTS_FOUND} from '../../constants/strings'; import {PreviewType, ScreenType} from '../../types'; -import {normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; +import {normalize, SCREEN_WIDTH} from '../../utils'; import SearchResultsCell from './SearchResultCell'; +import {useBottomTabBarHeight} from '@react-navigation/bottom-tabs'; interface SearchResultsProps { - results: Array | undefined; - keyboardVisible: boolean; + // TODO: make sure results come in as same type, regardless of profile, category, badges + results: SectionListData[]; previewType: PreviewType; screenType: ScreenType; } @@ -21,11 +29,8 @@ const sectionHeader: React.FC = (showBorder: Boolean) => { return null; }; -const SearchResultList: React.FC = ({ - results, - keyboardVisible, -}) => { - const [showEmptyView, setshowEmptyView] = useState(false); +const SearchResultList: React.FC = ({results}) => { + const [showEmptyView, setshowEmptyView] = useState(false); const {user: loggedInUser} = useSelector((state: RootState) => state.user); useEffect(() => { @@ -38,57 +43,41 @@ const SearchResultList: React.FC = ({ } }, [results]); - return ( - - {showEmptyView && ( - - {NO_RESULTS_FOUND} - - )} - {!showEmptyView && ( - item.id + index} - renderItem={({section, item}) => { - return ( - - ); - }} - renderSectionHeader={({section: {data}}) => - sectionHeader(data.length !== 0) - } - /> - )} + return showEmptyView ? ( + + {NO_RESULTS_FOUND} + ) : ( + item.id + index} + renderItem={({item}) => { + return ( + + ); + }} + renderSectionHeader={({section: {data}}) => + sectionHeader(data.length !== 0) + } + stickySectionHeadersEnabled={false} + /> ); }; const styles = StyleSheet.create({ container: { - height: SCREEN_HEIGHT, - paddingBottom: SCREEN_HEIGHT * 0.1, - }, - sectionListContentContainer: { - paddingBottom: SCREEN_HEIGHT * 0.15, - width: SCREEN_WIDTH, + flex: 1, + marginTop: 30, + alignItems: 'center', }, sectionHeaderStyle: { width: '100%', height: 0.5, - marginBottom: normalize(24), + marginVertical: 5, backgroundColor: '#C4C4C4', }, - keyboardOpen: { - marginBottom: SCREEN_HEIGHT * 0.35, - }, noResultsTextContainer: { justifyContent: 'center', flexDirection: 'row', diff --git a/src/components/search/SearchResults.tsx b/src/components/search/SearchResults.tsx index ef518d8b..a73d0b40 100644 --- a/src/components/search/SearchResults.tsx +++ b/src/components/search/SearchResults.tsx @@ -1,18 +1,10 @@ import React from 'react'; -import { - ProfilePreviewType, - PreviewType, - ScreenType, - CategoryPreviewType, -} from '../../types'; -import {View} from 'react-native'; +import {ProfilePreviewType, CategoryPreviewType} from '../../types'; import SearchResultsCell from './SearchResultCell'; import {useSelector} from 'react-redux'; import {RootState} from '../../store/rootReducer'; interface SearchResultsProps { results: ProfilePreviewType[]; - previewType: PreviewType; - screenType: ScreenType; categories: CategoryPreviewType[]; } const SearchResults: React.FC = ({results, categories}) => { @@ -22,7 +14,7 @@ const SearchResults: React.FC = ({results, categories}) => { */ const {user: loggedInUser} = useSelector((state: RootState) => state.user); return ( - + <> {categories .slice(0) .reverse() @@ -43,7 +35,7 @@ const SearchResults: React.FC = ({results, categories}) => { {...{loggedInUser}} /> ))} - + ); }; diff --git a/src/components/search/SearchResultsBackground.tsx b/src/components/search/SearchResultsBackground.tsx index 3d7fab4e..e5236295 100644 --- a/src/components/search/SearchResultsBackground.tsx +++ b/src/components/search/SearchResultsBackground.tsx @@ -1,32 +1,55 @@ import React from 'react'; -import {StyleSheet} from 'react-native'; -import Animated, {interpolate} from 'react-native-reanimated'; -import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; +import {StyleSheet, ViewStyle} from 'react-native'; +import Animated, { + useAnimatedStyle, + useDerivedValue, + interpolate, + Extrapolate, +} from 'react-native-reanimated'; +import {useSafeAreaInsets} from 'react-native-safe-area-context'; interface SearchResultsBackgroundProps { - top: Animated.Value; + animationProgress: Animated.SharedValue; + searchBarHeight: number; + searching: boolean; } const SearchResultsBackground: React.FC = ({ - top, + animationProgress, + searchBarHeight, + searching, children, }) => { - // TODO: (Leon) use reanimated v2 - const opacityBackground = 0; - // const opacityBackground: Animated.Node = interpolate(top, { - // inputRange: [-SCREEN_HEIGHT, 0], - // outputRange: [0, 1], - // }); - // TODO: (Leon) use reanimated v2 - const opacityContent = 0; - // const opacityContent: Animated.Node = interpolate(top, { - // inputRange: [-SCREEN_HEIGHT / 40, 0], - // outputRange: [0, 1], - // }); + const {top: topInset} = useSafeAreaInsets(); + /* + * On-search container style (opacity fade-in). + */ + const backgroundAnimatedStyles = useAnimatedStyle(() => ({ + opacity: animationProgress.value, + })); + /* + * Derived animation value for contentAnimatedStyles. + */ + const contentAnimationProgress = useDerivedValue(() => + interpolate(animationProgress.value, [0.9, 1], [0, 1], Extrapolate.CLAMP), + ); + /* + * On-search content style (delayed opacity fade-in). + */ + const contentAnimatedStyles = useAnimatedStyle(() => ({ + opacity: contentAnimationProgress.value, + })); return ( - + style={[ + styles.container, + backgroundAnimatedStyles, + { + // absolute: inset + search screen paddingTop + searchBar + padding + paddingTop: topInset + 15 + searchBarHeight + 10, + }, + ]} + pointerEvents={searching ? 'auto' : 'none'}> + {children} @@ -34,15 +57,11 @@ const SearchResultsBackground: React.FC = ({ }; const styles = StyleSheet.create({ container: { - height: SCREEN_HEIGHT, - width: SCREEN_WIDTH, - position: 'absolute', + ...StyleSheet.absoluteFillObject, backgroundColor: 'white', }, contentContainer: { flex: 1, - paddingVertical: 10, - paddingBottom: SCREEN_HEIGHT / 15, }, }); export default SearchResultsBackground; 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 = ({route}) => { ListFooterComponent={() => ( <> Other Groups - + )} /> 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(''); - const [results, setResults] = useState | undefined>(undefined); + const [results, setResults] = useState[] | undefined>(); const [recents, setRecents] = useState>( 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(0); + const [searchBarHeight, setSearchBarHeight] = useState(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 ( - + { onBlur={handleBlur} onFocus={handleFocus} value={query} - {...{top, searching}} + onLayout={onSearchBarLayout} + {...{animationProgress, searching}} /> - - - - {results === undefined && - recents.length + recentCategories.length !== 0 ? ( + + + {results === undefined ? ( + recents.length + recentCategories.length > 0 && ( - ) : ( - - )} - - + ) + ) : ( + + )} + ); }; 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; -- cgit v1.2.3-70-g09d2 From cce0f8510ac691618c69d76daacac4752800b8c1 Mon Sep 17 00:00:00 2001 From: Leon Jiang <35908040+leonyjiang@users.noreply.github.com> Date: Sat, 3 Apr 2021 20:54:09 -0400 Subject: Fix profile socials bar animation --- src/components/profile/Content.tsx | 56 ++++----- src/components/profile/Cover.tsx | 2 +- src/components/suggestedPeople/SPTaggsBar.tsx | 133 +++++++++++++++++++++ src/components/suggestedPeople/index.ts | 1 + src/components/taggs/TaggPostFooter.tsx | 2 +- src/components/taggs/TaggsBar.tsx | 93 +++++++------- src/screens/profile/ProfileScreen.tsx | 12 +- src/screens/suggestedPeople/SPBody.tsx | 20 +--- .../suggestedPeople/SuggestedPeopleScreen.tsx | 2 +- src/types/types.ts | 9 -- yarn.lock | 28 ++--- 11 files changed, 221 insertions(+), 137 deletions(-) create mode 100644 src/components/suggestedPeople/SPTaggsBar.tsx (limited to 'src/components') diff --git a/src/components/profile/Content.tsx b/src/components/profile/Content.tsx index 9c33eabc..dd68ab17 100644 --- a/src/components/profile/Content.tsx +++ b/src/components/profile/Content.tsx @@ -1,14 +1,10 @@ import React, {useCallback, useEffect, useRef, useState} from 'react'; -import { - LayoutChangeEvent, - NativeScrollEvent, - NativeSyntheticEvent, - RefreshControl, - StyleSheet, -} from 'react-native'; -import Animated from 'react-native-reanimated'; +import {LayoutChangeEvent, RefreshControl, StyleSheet} from 'react-native'; +import Animated, { + useSharedValue, + useAnimatedScrollHandler, +} from 'react-native-reanimated'; import {useDispatch, useSelector, useStore} from 'react-redux'; -import {COVER_HEIGHT} from '../../constants'; import { blockUnblockUser, loadFriendsData, @@ -20,12 +16,11 @@ import { NO_USER, } from '../../store/initialStates'; import {RootState} from '../../store/rootreducer'; -import {ContentProps} from '../../types'; +import {ScreenType} from '../../types'; import { canViewProfile, fetchUserX, getUserAsProfilePreviewType, - SCREEN_HEIGHT, userLogin, } from '../../utils'; import TaggsBar from '../taggs/TaggsBar'; @@ -35,8 +30,13 @@ import ProfileBody from './ProfileBody'; import ProfileCutout from './ProfileCutout'; import ProfileHeader from './ProfileHeader'; import PublicProfile from './PublicProfile'; +import {useScrollToTop} from '@react-navigation/native'; -const Content: React.FC = ({y, userXId, screenType}) => { +interface ContentProps { + userXId: string | undefined; + screenType: ScreenType; +} +const Content: React.FC = ({userXId, screenType}) => { const dispatch = useDispatch(); const { user = NO_USER, @@ -60,13 +60,13 @@ const Content: React.FC = ({y, userXId, screenType}) => { * If scrolling is enabled. Set to false before scrolling up for the tutorial. */ const [scrollEnabled, setScrollEnabled] = useState(true); + const y = useSharedValue(0); /** * States */ const [isBlocked, setIsBlocked] = useState(false); const [profileBodyHeight, setProfileBodyHeight] = useState(0); - const [shouldBounce, setShouldBounce] = useState(true); const [refreshing, setRefreshing] = useState(false); const onRefresh = useCallback(() => { @@ -103,45 +103,32 @@ const Content: React.FC = ({y, userXId, screenType}) => { * updateUserXFriends updates friends list for the user. */ const handleBlockUnblock = async (callback?: () => void) => { - await dispatch( + dispatch( blockUnblockUser( loggedInUser, getUserAsProfilePreviewType(user, profile), isBlocked, ), ); - await dispatch(loadFriendsData(loggedInUser.userId)); - await dispatch(updateUserXFriends(user.userId, state)); + dispatch(loadFriendsData(loggedInUser.userId)); + dispatch(updateUserXFriends(user.userId, state)); if (callback) { callback(); } }; - const handleScroll = (e: NativeSyntheticEvent) => { - /** - * Set the new y position - */ - const newY = e.nativeEvent.contentOffset.y; - y.setValue(newY); + const scrollHandler = useAnimatedScrollHandler((event) => { + y.value = event.contentOffset.y; + }); - /** - * Do not allow overflow of scroll on bottom of the screen - * SCREEN_HEIGHT - COVER_HEIGHT = Height of the scroll view - */ - if (newY >= SCREEN_HEIGHT - COVER_HEIGHT) { - setShouldBounce(false); - } else if (newY === 0) { - setShouldBounce(true); - } - }; + useScrollToTop(scrollViewRef); return ( handleScroll(e)} - bounces={shouldBounce} + onScroll={scrollHandler} showsVerticalScrollIndicator={false} scrollEventThrottle={1} stickyHeaderIndices={[4]} @@ -165,7 +152,6 @@ const Content: React.FC = ({y, userXId, screenType}) => { /> {canViewProfile(state, userXId, screenType) ? ( = ({userXId, screenType}) => { const styles = StyleSheet.create({ container: { - position: 'absolute', + ...StyleSheet.absoluteFillObject, }, image: { width: IMAGE_WIDTH, diff --git a/src/components/suggestedPeople/SPTaggsBar.tsx b/src/components/suggestedPeople/SPTaggsBar.tsx new file mode 100644 index 00000000..adac6dcf --- /dev/null +++ b/src/components/suggestedPeople/SPTaggsBar.tsx @@ -0,0 +1,133 @@ +import React, {useEffect, useState} from 'react'; +import {StyleSheet} from 'react-native'; +import Animated from 'react-native-reanimated'; +import {useDispatch, useSelector, useStore} from 'react-redux'; +import {INTEGRATED_SOCIAL_LIST, SOCIAL_LIST} from '../../constants'; +import {getLinkedSocials} from '../../services'; +import {loadIndividualSocial, updateSocial} from '../../store/actions'; +import {RootState} from '../../store/rootReducer'; +import {ScreenType} from '../../types'; +import {canViewProfile} from '../../utils'; +import Tagg from '../taggs/Tagg'; + +const {View, ScrollView} = Animated; +interface TaggsBarProps { + userXId: string | undefined; + screenType: ScreenType; + linkedSocials?: string[]; +} +const TaggsBar: React.FC = ({ + userXId, + screenType, + linkedSocials, +}) => { + let [taggs, setTaggs] = useState([]); + let [taggsNeedUpdate, setTaggsNeedUpdate] = useState(true); + const {user} = useSelector((state: RootState) => + userXId ? state.userX[screenType][userXId] : state.user, + ); + const state: RootState = useStore().getState(); + const allowTaggsNavigation = canViewProfile(state, userXId, screenType); + + const dispatch = useDispatch(); + + /** + * Updates the individual social that needs update + * If username is empty, update nonintegrated socials like Snapchat and TikTok + * @param socialType Type of the social that needs update + */ + const handleSocialUpdate = (socialType: string, username: string) => { + if (username !== '') { + dispatch(updateSocial(socialType, username)); + } else { + dispatch(loadIndividualSocial(user.userId, socialType)); + } + }; + + /** + * This useEffect should be called evey time the user being viewed is changed OR + * And update is triggered manually + */ + useEffect(() => { + const loadData = async () => { + const socials: string[] = linkedSocials + ? linkedSocials + : await getLinkedSocials(user.userId); + const unlinkedSocials = SOCIAL_LIST.filter( + (s) => socials.indexOf(s) === -1, + ); + let new_taggs = []; + let i = 0; + for (let social of socials) { + new_taggs.push( + , + ); + i++; + } + if (!userXId) { + for (let social of unlinkedSocials) { + new_taggs.push( + , + ); + i++; + } + } + setTaggs(new_taggs); + setTaggsNeedUpdate(false); + }; + if (user.userId) { + loadData(); + } + }, [taggsNeedUpdate, user]); + + return taggs.length > 0 ? ( + + + {taggs} + + + ) : ( + <> + ); +}; + +const styles = StyleSheet.create({ + spContainer: { + shadowColor: '#000', + shadowRadius: 10, + shadowOffset: {width: 0, height: 2}, + zIndex: 1, + marginBottom: 25, + }, + contentContainer: { + alignItems: 'center', + paddingBottom: 5, + }, +}); + +export default TaggsBar; diff --git a/src/components/suggestedPeople/index.ts b/src/components/suggestedPeople/index.ts index 515f6fb4..339c9ae0 100644 --- a/src/components/suggestedPeople/index.ts +++ b/src/components/suggestedPeople/index.ts @@ -1,2 +1,3 @@ export {default as MutualFriends} from './MutualFriends'; export {default as BadgesDropdown} from './BadgesDropdown'; +export {default as SPTaggsBar} from './SPTaggsBar'; diff --git a/src/components/taggs/TaggPostFooter.tsx b/src/components/taggs/TaggPostFooter.tsx index ae9d889d..750f1793 100644 --- a/src/components/taggs/TaggPostFooter.tsx +++ b/src/components/taggs/TaggPostFooter.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {Linking, StyleSheet, View} from 'react-native'; +import {StyleSheet, View} from 'react-native'; import {Text} from 'react-native-animatable'; import {handleOpenSocialUrlOnBrowser} from '../../utils'; import {DateLabel} from '../common'; diff --git a/src/components/taggs/TaggsBar.tsx b/src/components/taggs/TaggsBar.tsx index a87da3e2..87dabc3d 100644 --- a/src/components/taggs/TaggsBar.tsx +++ b/src/components/taggs/TaggsBar.tsx @@ -1,12 +1,16 @@ -import React, {Fragment, useEffect, useState} from 'react'; +import React, {useEffect, useState} from 'react'; import {StyleSheet} from 'react-native'; -import Animated from 'react-native-reanimated'; -import {useSafeAreaInsets} from 'react-native-safe-area-context'; +import Animated, { + useDerivedValue, + interpolate, + Extrapolate, + useAnimatedStyle, +} from 'react-native-reanimated'; import {useDispatch, useSelector, useStore} from 'react-redux'; import { INTEGRATED_SOCIAL_LIST, - PROFILE_CUTOUT_BOTTOM_Y, SOCIAL_LIST, + PROFILE_CUTOUT_BOTTOM_Y, } from '../../constants'; import {getLinkedSocials} from '../../services'; import {loadIndividualSocial, updateSocial} from '../../store/actions'; @@ -14,14 +18,14 @@ import {RootState} from '../../store/rootReducer'; import {ScreenType} from '../../types'; import {canViewProfile} from '../../utils'; import Tagg from './Tagg'; +import {useSafeAreaInsets} from 'react-native-safe-area-context'; -const {View, ScrollView, interpolate, Extrapolate} = Animated; +const {View, ScrollView} = Animated; interface TaggsBarProps { - y: Animated.Value; + y: Animated.SharedValue; profileBodyHeight: number; userXId: string | undefined; screenType: ScreenType; - whiteRing: boolean | undefined; linkedSocials?: string[]; } const TaggsBar: React.FC = ({ @@ -29,7 +33,6 @@ const TaggsBar: React.FC = ({ profileBodyHeight, userXId, screenType, - whiteRing, linkedSocials, }) => { let [taggs, setTaggs] = useState([]); @@ -41,7 +44,7 @@ const TaggsBar: React.FC = ({ const allowTaggsNavigation = canViewProfile(state, userXId, screenType); const dispatch = useDispatch(); - + const insetTop = useSafeAreaInsets().top; /** * Updates the individual social that needs update * If username is empty, update nonintegrated socials like Snapchat and TikTok @@ -80,13 +83,13 @@ const TaggsBar: React.FC = ({ isIntegrated={INTEGRATED_SOCIAL_LIST.indexOf(social) !== -1} setTaggsNeedUpdate={setTaggsNeedUpdate} setSocialDataNeedUpdate={handleSocialUpdate} - whiteRing={whiteRing ? whiteRing : undefined} + whiteRing={false} allowNavigation={allowTaggsNavigation} />, ); i++; } - if (!userXId && !whiteRing) { + if (!userXId) { for (let social of unlinkedSocials) { new_taggs.push( = ({ setSocialDataNeedUpdate={handleSocialUpdate} userXId={userXId} user={user} - whiteRing={whiteRing ? whiteRing : undefined} + whiteRing={false} allowNavigation={allowTaggsNavigation} />, ); @@ -112,67 +115,55 @@ const TaggsBar: React.FC = ({ loadData(); } }, [taggsNeedUpdate, user]); -// TODO: (Leon) use reanimated v2 - const shadowOpacity = 0; - // const shadowOpacity: Animated.Node = interpolate(y, { - // inputRange: [ - // PROFILE_CUTOUT_BOTTOM_Y + profileBodyHeight, - // PROFILE_CUTOUT_BOTTOM_Y + profileBodyHeight + 20, - // ], - // outputRange: [0, 0.2], - // extrapolate: Extrapolate.CLAMP, - // }); - // TODO: (Leon) use reanimated v2 - const paddingTop = 0; - // const paddingTop: Animated.Node = interpolate(y, { - // inputRange: [ - // PROFILE_CUTOUT_BOTTOM_Y + - // profileBodyHeight - - // (useSafeAreaInsets().top + 10), - // PROFILE_CUTOUT_BOTTOM_Y + profileBodyHeight, - // ], - // outputRange: [10, useSafeAreaInsets().top], - // extrapolate: Extrapolate.CLAMP, - // }); + const paddingTopStylesProgress = useDerivedValue(() => + interpolate( + y.value, + [PROFILE_CUTOUT_BOTTOM_Y, PROFILE_CUTOUT_BOTTOM_Y + profileBodyHeight], + [0, 1], + Extrapolate.CLAMP, + ), + ); + const shadowOpacityStylesProgress = useDerivedValue(() => + interpolate( + y.value, + [ + PROFILE_CUTOUT_BOTTOM_Y + profileBodyHeight, + PROFILE_CUTOUT_BOTTOM_Y + profileBodyHeight + insetTop, + ], + [0, 1], + Extrapolate.CLAMP, + ), + ); + const animatedStyles = useAnimatedStyle(() => ({ + shadowOpacity: shadowOpacityStylesProgress.value / 5, + paddingTop: paddingTopStylesProgress.value * insetTop, + })); return taggs.length > 0 ? ( - + + contentContainerStyle={[styles.contentContainer]}> {taggs} ) : ( - + <> ); }; const styles = StyleSheet.create({ - spContainer: { - shadowColor: '#000', - shadowRadius: 10, - shadowOffset: {width: 0, height: 2}, - zIndex: 1, - marginBottom: 25, - }, container: { backgroundColor: 'white', shadowColor: '#000', shadowRadius: 10, shadowOffset: {width: 0, height: 2}, zIndex: 1, - paddingBottom: 5, }, contentContainer: { alignItems: 'center', - paddingBottom: 5, + paddingBottom: 15, }, }); diff --git a/src/screens/profile/ProfileScreen.tsx b/src/screens/profile/ProfileScreen.tsx index 313e2f2c..a5d1e495 100644 --- a/src/screens/profile/ProfileScreen.tsx +++ b/src/screens/profile/ProfileScreen.tsx @@ -1,12 +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 @@ -22,8 +18,6 @@ interface ProfileOnboardingProps { const ProfileScreen: React.FC = ({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. @@ -47,7 +41,7 @@ const ProfileScreen: React.FC = ({route}) => { return ( <> - + ); 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 = ({ }[] >([]); 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 = ({ {user.id !== loggedInUserId && } - 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)} diff --git a/src/types/types.ts b/src/types/types.ts index b5dc6373..40f2a614 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -1,4 +1,3 @@ -import Animated from 'react-native-reanimated'; import {Channel as ChannelType, StreamChat} from 'stream-chat'; export interface UserType { @@ -210,14 +209,6 @@ export interface CommentNotificationType { export interface ThreadNotificationType extends CommentNotificationType { parent_comment: string; } -export interface ContentProps { - y: Animated.Value; - userXId: string | undefined; - screenType: ScreenType; - setScrollEnabled: (enabled: boolean) => void; - profileBodyHeight: number; - scrollViewRef: React.MutableRefObject; -} export type NotificationType = { actor: ProfilePreviewType; diff --git a/yarn.lock b/yarn.lock index 26b300f1..2e9f711a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1053,17 +1053,17 @@ integrity sha512-DVgibb4XERG+5TQQiHKJ1qF+5cbpfiKGRFze9WcbE+FxSYwNMIzyt6u3l7WMBLz6wXcqAq3zLRo6SBbSt37uDw== "@react-navigation/bottom-tabs@^5.7.2": - version "5.11.8" - resolved "https://registry.yarnpkg.com/@react-navigation/bottom-tabs/-/bottom-tabs-5.11.8.tgz#0af8d5859b1ad781bdd86e8236ceaed60dc355fe" - integrity sha512-/0AiP+QPGKe35NdGxSjq0XpZjYvURy8H2E4rZMRHJ+fUoq+cX0ze/XQeQ8IHMpoc568ZwOGRgdUToUGZEDMpvA== + version "5.11.9" + resolved "https://registry.yarnpkg.com/@react-navigation/bottom-tabs/-/bottom-tabs-5.11.9.tgz#380a97593290a8e278365c5e4d84813e19c8a9c9" + integrity sha512-RuSlULJrEiSCDLTi3OfmXqfmBm+Y0G4JnGiCUguMDet+x5AXdYhrsLdxJVyBsnoQz3lvlz9pWIcQ8p/hPiv2CA== dependencies: color "^3.1.3" react-native-iphone-x-helper "^1.3.0" -"@react-navigation/core@^5.15.2": - version "5.15.2" - resolved "https://registry.yarnpkg.com/@react-navigation/core/-/core-5.15.2.tgz#6aa374c7bcb6ffcaac8e2a7f8bdb2f9aba469b31" - integrity sha512-jNSP0FMu1N6Pa1Slsy8b/JbmlTAXcVeXVwnxrEMVGWeiNqUVYl+tx1FuQAqi3q1m4cg9ygXkGsgLgRmnXAEC8g== +"@react-navigation/core@^5.15.3": + version "5.15.3" + resolved "https://registry.yarnpkg.com/@react-navigation/core/-/core-5.15.3.tgz#dce7090bf3ea0d302993d742c706825e495b812e" + integrity sha512-3ZdyDInh8qg1kygCNkmh9lFgpDf29lTvPsaMe2mm/qvmxLKSgttWBz07P2fc181aV9jTdgQpzYfWZ5KWT036zw== dependencies: "@react-navigation/routers" "^5.7.2" escape-string-regexp "^4.0.0" @@ -1072,11 +1072,11 @@ react-is "^16.13.0" "@react-navigation/native@^5.6.1": - version "5.9.3" - resolved "https://registry.yarnpkg.com/@react-navigation/native/-/native-5.9.3.tgz#3859f439adc9a744b79a98fbc7606bdd459574d5" - integrity sha512-xaRlCDRVuFGxHsP/IetwLdNvLJwIJBYCUIx/ufWs6QkT9Q0EB0DtKzXCItuHydjMEVPd1Cy7lfjUlSM6hZ6Q3Q== + version "5.9.4" + resolved "https://registry.yarnpkg.com/@react-navigation/native/-/native-5.9.4.tgz#414c044423c58aa1cdde1b6494309e0b51da08b8" + integrity sha512-BUCrOXfZDdKWBqM8OhOKQhCX5we4HUo5XG6tCQtVqQAep+7UcApZmMUuemUXDxVe8NPESUpoUlB0RaEpyIdfTQ== dependencies: - "@react-navigation/core" "^5.15.2" + "@react-navigation/core" "^5.15.3" escape-string-regexp "^4.0.0" nanoid "^3.1.15" @@ -1088,9 +1088,9 @@ nanoid "^3.1.15" "@react-navigation/stack@^5.6.2": - version "5.14.3" - resolved "https://registry.yarnpkg.com/@react-navigation/stack/-/stack-5.14.3.tgz#3d15fcd2cf8d0d2a1248686565c6a85e2d8e1c55" - integrity sha512-7rHc13DHsYP7l7GcgBcLEyX2/IAuCcRZ1Iu3MtOZSayjvFXxBBYKFKw0OyY9NxOfZUdLl3Q3mLiUHVFZkHMcuA== + version "5.14.4" + resolved "https://registry.yarnpkg.com/@react-navigation/stack/-/stack-5.14.4.tgz#32f6717c03494f0ca6cf0dd43d8302af824de9e9" + integrity sha512-gQjWK8JHtVkD1p7wzjtSPuScJI0mSAk/N/gzgjQZo+rDUwgM8rOTDcVNRbtEOqCEgLQcZrZQHwhOjkrJirehjQ== dependencies: color "^3.1.3" react-native-iphone-x-helper "^1.3.0" -- cgit v1.2.3-70-g09d2 From 1cd94d9a012543683126094251b2e5562e2eea21 Mon Sep 17 00:00:00 2001 From: Leon Jiang <35908040+leonyjiang@users.noreply.github.com> Date: Sat, 3 Apr 2021 21:25:32 -0400 Subject: Fix bottom sheet animation --- src/components/common/BottomDrawer.tsx | 19 ++++++------------- src/routes/main/MainStackScreen.tsx | 18 ++++++++---------- 2 files changed, 14 insertions(+), 23 deletions(-) (limited to 'src/components') diff --git a/src/components/common/BottomDrawer.tsx b/src/components/common/BottomDrawer.tsx index 173823b1..988c1e79 100644 --- a/src/components/common/BottomDrawer.tsx +++ b/src/components/common/BottomDrawer.tsx @@ -6,7 +6,7 @@ import { View, ViewProps, } from 'react-native'; -import Animated from 'react-native-reanimated'; +import Animated, {interpolateColors} from 'react-native-reanimated'; import BottomSheet from 'reanimated-bottom-sheet'; import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; @@ -50,6 +50,10 @@ const BottomDrawer: React.FC = (props) => { ); }; + const backgroundColor = interpolateColors(bgAlpha, { + inputRange: [0, 1], + outputColorRange: ['rgba(0,0,0,0.3)', 'rgba(0,0,0,0)'], + }); return ( = (props) => { onPress={() => { setIsOpen(false); }}> - + ); diff --git a/src/routes/main/MainStackScreen.tsx b/src/routes/main/MainStackScreen.tsx index b72ab779..8068b893 100644 --- a/src/routes/main/MainStackScreen.tsx +++ b/src/routes/main/MainStackScreen.tsx @@ -98,11 +98,10 @@ const MainStackScreen: React.FC = ({route}) => { cardOverlayEnabled: true, cardStyleInterpolator: ({current: {progress}}) => ({ cardStyle: { - // TODO: (Leon) use reanimated v2 - // opacity: progress.interpolate({ - // inputRange: [0, 0.5, 0.9, 1], - // outputRange: [0, 0.25, 0.7, 1], - // }), + opacity: progress.interpolate({ + inputRange: [0, 0.5, 0.9, 1], + outputRange: [0, 0.25, 0.7, 1], + }), }, }), }; @@ -349,11 +348,10 @@ export const modalStyle: StackNavigationOptions = { cardOverlayEnabled: true, cardStyleInterpolator: ({current: {progress}}) => ({ cardStyle: { - // TODO: (Leon) use reanimated v2 - // opacity: progress.interpolate({ - // inputRange: [0, 0.5, 0.9, 1], - // outputRange: [0, 0.25, 0.7, 1], - // }), + opacity: progress.interpolate({ + inputRange: [0, 0.5, 0.9, 1], + outputRange: [0, 0.25, 0.7, 1], + }), }, }), }; -- cgit v1.2.3-70-g09d2 From 88d1ec9dff5674e8759ca33e4255af16b4bf51a5 Mon Sep 17 00:00:00 2001 From: Ivan Chen Date: Mon, 5 Apr 2021 15:29:11 -0400 Subject: code cleanup --- src/components/messages/MessagesHeader.tsx | 4 +--- src/screens/chat/ChatListScreen.tsx | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) (limited to 'src/components') diff --git a/src/components/messages/MessagesHeader.tsx b/src/components/messages/MessagesHeader.tsx index 3b8144f7..d8445580 100644 --- a/src/components/messages/MessagesHeader.tsx +++ b/src/components/messages/MessagesHeader.tsx @@ -16,9 +16,7 @@ const MessagesHeader: React.FC = ({createChannel}) => { { - createChannel(); - }}> + onPress={createChannel}> Compose diff --git a/src/screens/chat/ChatListScreen.tsx b/src/screens/chat/ChatListScreen.tsx index 8bced236..8fa6998c 100644 --- a/src/screens/chat/ChatListScreen.tsx +++ b/src/screens/chat/ChatListScreen.tsx @@ -68,7 +68,6 @@ const ChatListScreen: React.FC = ({navigation}) => { { - // TODO: (CHAT) fix type issue setChannel(channel); navigation.navigate('Chat'); }} -- cgit v1.2.3-70-g09d2 From 8f0eb0703f24076796fa6c13e35b5fc8b8de87ab Mon Sep 17 00:00:00 2001 From: Ivan Chen Date: Tue, 6 Apr 2021 15:58:44 -0400 Subject: added compose button asset --- src/assets/icons/compose.svg | 1 + src/components/messages/MessagesHeader.tsx | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 src/assets/icons/compose.svg (limited to 'src/components') diff --git a/src/assets/icons/compose.svg b/src/assets/icons/compose.svg new file mode 100644 index 00000000..062e08cf --- /dev/null +++ b/src/assets/icons/compose.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/messages/MessagesHeader.tsx b/src/components/messages/MessagesHeader.tsx index d8445580..a3b31a22 100644 --- a/src/components/messages/MessagesHeader.tsx +++ b/src/components/messages/MessagesHeader.tsx @@ -3,6 +3,7 @@ import {StyleSheet, View} from 'react-native'; import {Text} from 'react-native-animatable'; import {TouchableOpacity} from 'react-native-gesture-handler'; import {normalize} from '../../utils'; +import ComposeIcon from '../../assets/icons/compose.svg'; type MessagesHeaderProps = { createChannel: () => void; @@ -14,10 +15,9 @@ const MessagesHeader: React.FC = ({createChannel}) => { Messages 2 unread - - Compose + + {/* Compose */} + ); -- cgit v1.2.3-70-g09d2 From 952329c1ee1ebeee27d86d3dc09f4ceaa0b9f6a1 Mon Sep 17 00:00:00 2001 From: Ivan Chen Date: Wed, 7 Apr 2021 15:23:34 -0400 Subject: added unread count --- src/components/messages/MessagesHeader.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'src/components') diff --git a/src/components/messages/MessagesHeader.tsx b/src/components/messages/MessagesHeader.tsx index a3b31a22..2b20f48c 100644 --- a/src/components/messages/MessagesHeader.tsx +++ b/src/components/messages/MessagesHeader.tsx @@ -1,22 +1,26 @@ -import * as React from 'react'; +import React, {useContext} from 'react'; import {StyleSheet, View} from 'react-native'; import {Text} from 'react-native-animatable'; import {TouchableOpacity} from 'react-native-gesture-handler'; import {normalize} from '../../utils'; import ComposeIcon from '../../assets/icons/compose.svg'; +import {ChatContext} from '../../App'; type MessagesHeaderProps = { createChannel: () => void; }; const MessagesHeader: React.FC = ({createChannel}) => { + const {chatClient} = useContext(ChatContext); + const unread = chatClient.user?.total_unread_count as number; return ( Messages - 2 unread + {unread && unread !== 0 && ( + {unread} unread + )} - {/* Compose */} -- cgit v1.2.3-70-g09d2 From 6e456b97cbdc8c13b586a939ddcdfbf2587ed3cf Mon Sep 17 00:00:00 2001 From: Ivan Chen Date: Wed, 7 Apr 2021 16:58:23 -0400 Subject: updated unread styling --- src/components/messages/MessagesHeader.tsx | 4 +++- src/screens/chat/ChatListScreen.tsx | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'src/components') diff --git a/src/components/messages/MessagesHeader.tsx b/src/components/messages/MessagesHeader.tsx index 2b20f48c..18c89de3 100644 --- a/src/components/messages/MessagesHeader.tsx +++ b/src/components/messages/MessagesHeader.tsx @@ -17,7 +17,9 @@ const MessagesHeader: React.FC = ({createChannel}) => { Messages {unread && unread !== 0 && ( - {unread} unread + + {unread > 99 ? '99+' : unread} unread + )} diff --git a/src/screens/chat/ChatListScreen.tsx b/src/screens/chat/ChatListScreen.tsx index 9011ed4a..98a7f097 100644 --- a/src/screens/chat/ChatListScreen.tsx +++ b/src/screens/chat/ChatListScreen.tsx @@ -80,6 +80,7 @@ const ChatListScreen: React.FC = ({navigation}) => { watch: true, }} sort={{last_message_at: -1}} + maxUnreadCount={99} /> -- cgit v1.2.3-70-g09d2 From e3483bcf735c2a65ab53d5ee10e43ca6c5e33864 Mon Sep 17 00:00:00 2001 From: Ivan Chen Date: Wed, 7 Apr 2021 18:22:26 -0400 Subject: added figma styling --- src/components/messages/ChannelPreview.tsx | 136 +++++++++++++++++++++++++++++ src/components/messages/MessagesHeader.tsx | 6 +- src/components/messages/index.ts | 1 + src/screens/chat/ChatListScreen.tsx | 30 +++++-- src/types/types.ts | 1 + 5 files changed, 164 insertions(+), 10 deletions(-) create mode 100644 src/components/messages/ChannelPreview.tsx (limited to 'src/components') diff --git a/src/components/messages/ChannelPreview.tsx b/src/components/messages/ChannelPreview.tsx new file mode 100644 index 00000000..11408dc1 --- /dev/null +++ b/src/components/messages/ChannelPreview.tsx @@ -0,0 +1,136 @@ +import {useNavigation} from '@react-navigation/core'; +import React, {useContext} from 'react'; +import {Image, StyleSheet, Text, View} from 'react-native'; +import {TouchableOpacity} from 'react-native-gesture-handler'; +import {useSelector} from 'react-redux'; +import {ChannelPreviewMessengerProps} from 'stream-chat-react-native'; +import {ChatContext} from '../../App'; +import {RootState} from '../../store/rootReducer'; +import { + LocalAttachmentType, + LocalChannelType, + LocalCommandType, + LocalEventType, + LocalMessageType, + LocalReactionType, + LocalUserType, +} from '../../types'; +import {normalize, SCREEN_HEIGHT} from '../../utils'; + +const ChannelPreview: React.FC< + ChannelPreviewMessengerProps< + LocalAttachmentType, + LocalChannelType, + LocalCommandType, + LocalEventType, + LocalMessageType, + LocalReactionType, + LocalUserType + > +> = (props) => { + const navigation = useNavigation(); + const {setChannel} = useContext(ChatContext); + const {channel} = props; + const {userId: loggedInUserId} = useSelector( + (state: RootState) => state.user.user, + ); + const otherMembers = channel + ? Object.values(channel.state.members).filter( + (member) => member.user?.id !== loggedInUserId, + ) + : []; + const member = otherMembers.length === 1 ? otherMembers[0] : undefined; + const online = member?.user?.online; + const unread = channel.state.unreadCount > 0; + + return ( + { + setChannel(channel); + navigation.navigate('Chat'); + }}> + + + {online && } + + + + {member?.user?.first_name} {member?.user?.last_name} + + + {channel.state.messages[channel.state.messages.length - 1].text} + + + {unread && } + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + flexDirection: 'row', + height: Math.round(SCREEN_HEIGHT / 10), + alignItems: 'center', + paddingHorizontal: '8%', + paddingVertical: '5%', + }, + avatar: { + width: normalize(62), + height: normalize(62), + borderRadius: normalize(62) / 2, + }, + online: { + position: 'absolute', + backgroundColor: '#6EE7E7', + width: normalize(18), + height: normalize(18), + borderRadius: normalize(18) / 2, + borderColor: 'white', + borderWidth: 2, + bottom: 0, + right: 0, + }, + content: { + flex: 1, + height: '100%', + justifyContent: 'space-between', + flexDirection: 'column', + marginLeft: '5%', + }, + name: { + fontWeight: '500', + fontSize: normalize(14), + lineHeight: normalize(17), + }, + lastMessage: { + color: '#828282', + fontWeight: '500', + fontSize: normalize(12), + lineHeight: normalize(14), + }, + unread: { + fontWeight: '700', + color: 'black', + }, + purpleDot: { + backgroundColor: '#8F01FF', + width: normalize(10), + height: normalize(10), + borderRadius: normalize(10) / 2, + }, +}); + +export default ChannelPreview; diff --git a/src/components/messages/MessagesHeader.tsx b/src/components/messages/MessagesHeader.tsx index 18c89de3..660da97d 100644 --- a/src/components/messages/MessagesHeader.tsx +++ b/src/components/messages/MessagesHeader.tsx @@ -1,4 +1,4 @@ -import React, {useContext} from 'react'; +import React, {Fragment, useContext} from 'react'; import {StyleSheet, View} from 'react-native'; import {Text} from 'react-native-animatable'; import {TouchableOpacity} from 'react-native-gesture-handler'; @@ -16,10 +16,12 @@ const MessagesHeader: React.FC = ({createChannel}) => { return ( Messages - {unread && unread !== 0 && ( + {unread && unread !== 0 ? ( {unread > 99 ? '99+' : unread} unread + ) : ( + )} diff --git a/src/components/messages/index.ts b/src/components/messages/index.ts index 2d6bb581..e194093c 100644 --- a/src/components/messages/index.ts +++ b/src/components/messages/index.ts @@ -1 +1,2 @@ export {default as MessagesHeader} from './MessagesHeader'; +export {default as ChannelPreview} from './ChannelPreview'; diff --git a/src/screens/chat/ChatListScreen.tsx b/src/screens/chat/ChatListScreen.tsx index 98a7f097..637c6231 100644 --- a/src/screens/chat/ChatListScreen.tsx +++ b/src/screens/chat/ChatListScreen.tsx @@ -5,9 +5,18 @@ 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 {MessagesHeader} from '../../components/messages'; +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'; type ChatListScreenNavigationProp = StackNavigationProp< MainStackParams, @@ -19,8 +28,8 @@ interface ChatListScreenProps { /* * Screen that displays all of the user's active conversations. */ -const ChatListScreen: React.FC = ({navigation}) => { - const {chatClient, setChannel} = useContext(ChatContext); +const ChatListScreen: React.FC = () => { + const {chatClient} = useContext(ChatContext); const [clientReady, setClientReady] = useState(false); const state: RootState = useStore().getState(); const loggedInUserId = state.user.user.userId; @@ -68,12 +77,16 @@ const ChatListScreen: React.FC = ({navigation}) => { {clientReady && ( - filters={memoizedFilters} - onSelect={(channel) => { - setChannel(channel); - navigation.navigate('Chat'); - }} options={{ presence: true, state: true, @@ -81,6 +94,7 @@ const ChatListScreen: React.FC = ({navigation}) => { }} sort={{last_message_at: -1}} maxUnreadCount={99} + Preview={ChannelPreview} /> diff --git a/src/types/types.ts b/src/types/types.ts index 1a352808..582eefac 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -298,6 +298,7 @@ export type LocalCommandType = string; export type LocalEventType = Record; export type LocalMessageType = Record; export type LocalResponseType = Record; +export type LocalReactionType = Record; export type LocalUserType = Record; export type ChatContextType = { -- cgit v1.2.3-70-g09d2 From bb16e95e15f6ea9b8941cae764570bdf7c0fdb59 Mon Sep 17 00:00:00 2001 From: Ivan Chen Date: Thu, 8 Apr 2021 16:10:14 -0400 Subject: created util functions, updated isOnline --- src/components/messages/ChannelPreview.tsx | 3 +- src/screens/chat/ChatScreen.tsx | 11 ++++++ src/utils/messages.ts | 59 ++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 src/utils/messages.ts (limited to 'src/components') diff --git a/src/components/messages/ChannelPreview.tsx b/src/components/messages/ChannelPreview.tsx index 11408dc1..867e0a38 100644 --- a/src/components/messages/ChannelPreview.tsx +++ b/src/components/messages/ChannelPreview.tsx @@ -16,6 +16,7 @@ import { LocalUserType, } from '../../types'; import {normalize, SCREEN_HEIGHT} from '../../utils'; +import {isOnline} from '../../utils/messages'; const ChannelPreview: React.FC< ChannelPreviewMessengerProps< @@ -40,7 +41,7 @@ const ChannelPreview: React.FC< ) : []; const member = otherMembers.length === 1 ? otherMembers[0] : undefined; - const online = member?.user?.online; + const online = isOnline(member?.user?.last_active); const unread = channel.state.unreadCount > 0; return ( diff --git a/src/screens/chat/ChatScreen.tsx b/src/screens/chat/ChatScreen.tsx index eeb1a7d6..8e6c1575 100644 --- a/src/screens/chat/ChatScreen.tsx +++ b/src/screens/chat/ChatScreen.tsx @@ -2,6 +2,7 @@ import {useBottomTabBarHeight} from '@react-navigation/bottom-tabs'; import {StackNavigationProp, useHeaderHeight} from '@react-navigation/stack'; import React, {useContext} from 'react'; import {StyleSheet, View} from 'react-native'; +import {useSelector} from 'react-redux'; import { Channel, Chat, @@ -10,6 +11,7 @@ import { } from 'stream-chat-react-native'; import {ChatContext} from '../../App'; import {MainStackParams} from '../../routes'; +import {RootState} from '../../store/rootReducer'; type ChatScreenNavigationProp = StackNavigationProp; interface ChatScreenProps { @@ -22,6 +24,15 @@ const ChatScreen: React.FC = () => { const {channel, chatClient} = useContext(ChatContext); const headerHeight = useHeaderHeight(); const tabbarHeight = useBottomTabBarHeight(); + const {userId: loggedInUserId} = useSelector( + (state: RootState) => state.user.user, + ); + const otherMembers = channel + ? Object.values(channel.state.members).filter( + (member) => member.user?.id !== loggedInUserId, + ) + : []; + const member = otherMembers.length === 1 ? otherMembers[0] : undefined; return ( diff --git a/src/utils/messages.ts b/src/utils/messages.ts new file mode 100644 index 00000000..ae8e7cec --- /dev/null +++ b/src/utils/messages.ts @@ -0,0 +1,59 @@ +import moment from 'moment'; + +/** + * Finds the difference in time in minutes + * @param lastActive given time e.g. "2021-04-08T19:07:09.361300983Z" + * @returns diff in minutes + */ +const _diffInMinutes = (lastActive: string | undefined) => { + if (!lastActive) { + return undefined; + } + return moment().diff(moment(lastActive), 'minutes'); +}; + +/** + * Formats the last activity status. + * - "Active now" (≤ 5 minutes) + * - "Seen X minutes ago" (5 > x ≥ 59 minutes) + * - "Seen X hours ago" (x = [1, 2]) + * - "Offline" + * @param lastActive given time e.g. "2021-04-08T19:07:09.361300983Z" + * @returns + */ +export const formatLastSeenText = (lastActive: string | undefined) => { + const diff = _diffInMinutes(lastActive); + if (!diff) { + return 'Offline'; + } + if (diff <= 5) { + return 'Active now'; + } + if (diff <= 59) { + return `Seen ${diff} minutes ago`; + } + if (diff <= 180) { + const hours = Math.floor(diff / 60); + return `Seen ${hours} hours ago`; + } + return 'Offline'; +}; + +/** + * Checks if a lastActive timestamp is considered Online or not. + * + * A user is online if last active is ≤ 15 minutes. + * + * @param lastActive given time e.g. "2021-04-08T19:07:09.361300983Z" + * @returns True if active + */ +export const isOnline = (lastActive: string | undefined) => { + if (!lastActive) { + return false; + } + const diff = _diffInMinutes(lastActive); + if (!diff) { + return false; + } + return diff <= 15; +}; -- cgit v1.2.3-70-g09d2 From fab86f9b874524a4beabb3c45a9e59e8b00ca495 Mon Sep 17 00:00:00 2001 From: Ivan Chen Date: Thu, 8 Apr 2021 18:42:28 -0400 Subject: added chat header, created isMember util, fixed KB padding issue --- src/App.tsx | 15 ++---- src/components/messages/ChannelPreview.tsx | 25 ++++----- src/components/messages/ChatHeader.tsx | 84 ++++++++++++++++++++++++++++++ src/routes/main/MainStackScreen.tsx | 7 ++- src/screens/chat/ChatScreen.tsx | 31 +++++------ src/types/types.ts | 21 ++++---- src/utils/layouts.ts | 1 + src/utils/messages.ts | 24 +++++++++ 8 files changed, 151 insertions(+), 57 deletions(-) create mode 100644 src/components/messages/ChatHeader.tsx (limited to 'src/components') diff --git a/src/App.tsx b/src/App.tsx index 9510c193..b8d64461 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,13 +1,14 @@ import {NavigationContainer} from '@react-navigation/native'; import React, {useState} from 'react'; import {Provider} from 'react-redux'; -import {Channel as ChannelType, StreamChat} from 'stream-chat'; +import {StreamChat} from 'stream-chat'; import {OverlayProvider} from 'stream-chat-react-native'; import {STREAM_CHAT_API} from './constants'; import {navigationRef} from './RootNavigation'; import Routes from './routes'; import store from './store/configureStore'; import { + ChannelGroupedType, ChatContextType, LocalAttachmentType, LocalChannelType, @@ -21,17 +22,7 @@ import { export const ChatContext = React.createContext({} as ChatContextType); const App = () => { - const [channel, setChannel] = useState< - ChannelType< - LocalAttachmentType, - LocalChannelType, - LocalCommandType, - LocalEventType, - LocalMessageType, - LocalResponseType, - LocalUserType - > - >(); + const [channel, setChannel] = useState(); const chatClient = StreamChat.getInstance< LocalAttachmentType, LocalChannelType, diff --git a/src/components/messages/ChannelPreview.tsx b/src/components/messages/ChannelPreview.tsx index 867e0a38..8ec6060a 100644 --- a/src/components/messages/ChannelPreview.tsx +++ b/src/components/messages/ChannelPreview.tsx @@ -2,7 +2,7 @@ import {useNavigation} from '@react-navigation/core'; import React, {useContext} from 'react'; import {Image, StyleSheet, Text, View} from 'react-native'; import {TouchableOpacity} from 'react-native-gesture-handler'; -import {useSelector} from 'react-redux'; +import {useSelector, useStore} from 'react-redux'; import {ChannelPreviewMessengerProps} from 'stream-chat-react-native'; import {ChatContext} from '../../App'; import {RootState} from '../../store/rootReducer'; @@ -16,7 +16,7 @@ import { LocalUserType, } from '../../types'; import {normalize, SCREEN_HEIGHT} from '../../utils'; -import {isOnline} from '../../utils/messages'; +import {getMember, isOnline} from '../../utils/messages'; const ChannelPreview: React.FC< ChannelPreviewMessengerProps< @@ -29,18 +29,11 @@ const ChannelPreview: React.FC< LocalUserType > > = (props) => { - const navigation = useNavigation(); const {setChannel} = useContext(ChatContext); + const state = useStore().getState(); + const navigation = useNavigation(); const {channel} = props; - const {userId: loggedInUserId} = useSelector( - (state: RootState) => state.user.user, - ); - const otherMembers = channel - ? Object.values(channel.state.members).filter( - (member) => member.user?.id !== loggedInUserId, - ) - : []; - const member = otherMembers.length === 1 ? otherMembers[0] : undefined; + const member = getMember(channel, state); const online = isOnline(member?.user?.last_active); const unread = channel.state.unreadCount > 0; @@ -55,8 +48,8 @@ const ChannelPreview: React.FC< @@ -71,7 +64,9 @@ const ChannelPreview: React.FC< - {channel.state.messages[channel.state.messages.length - 1].text} + {channel.state.messages.length > 0 + ? channel.state.messages[channel.state.messages.length - 1].text + : ''} {unread && } diff --git a/src/components/messages/ChatHeader.tsx b/src/components/messages/ChatHeader.tsx new file mode 100644 index 00000000..2bc096ec --- /dev/null +++ b/src/components/messages/ChatHeader.tsx @@ -0,0 +1,84 @@ +import React, {useContext} from 'react'; +import {Image, StyleSheet, View} from 'react-native'; +import {Text} from 'react-native-animatable'; +import {useStore} from 'react-redux'; +import {ChatContext} from '../../App'; +import {ChatHeaderHeight, normalize, StatusBarHeight} from '../../utils'; +import {formatLastSeenText, getMember, isOnline} from '../../utils/messages'; + +type ChatHeaderProps = {}; + +const ChatHeader: React.FC = () => { + const {channel} = useContext(ChatContext); + const state = useStore().getState(); + const member = getMember(channel, state); + const online = isOnline(member?.user?.last_active); + const lastSeen = formatLastSeenText(member?.user?.last_active); + + return ( + + + + {online && } + + + + {member?.user?.first_name} {member?.user?.last_name} + + {lastSeen} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + height: ChatHeaderHeight - StatusBarHeight, + flexDirection: 'row', + alignItems: 'center', + paddingLeft: '15%', + }, + avatar: { + width: normalize(40), + height: normalize(40), + borderRadius: normalize(40) / 2, + }, + online: { + position: 'absolute', + backgroundColor: '#6EE7E7', + width: normalize(16), + height: normalize(16), + borderRadius: normalize(16) / 2, + borderColor: 'white', + borderWidth: 3, + top: 0, + right: 0, + }, + content: { + flex: 1, + height: '80%', + justifyContent: 'space-between', + flexDirection: 'column', + marginLeft: '5%', + }, + name: { + fontWeight: '700', + fontSize: normalize(15), + lineHeight: normalize(18), + }, + lastSeen: { + color: '#828282', + fontWeight: '500', + fontSize: normalize(12), + lineHeight: normalize(14), + }, +}); + +export default ChatHeader; diff --git a/src/routes/main/MainStackScreen.tsx b/src/routes/main/MainStackScreen.tsx index 8068b893..48c57920 100644 --- a/src/routes/main/MainStackScreen.tsx +++ b/src/routes/main/MainStackScreen.tsx @@ -34,7 +34,7 @@ import { } from '../../screens'; import MutualBadgeHolders from '../../screens/suggestedPeople/MutualBadgeHolders'; import {ScreenType} from '../../types'; -import {AvatarHeaderHeight, SCREEN_WIDTH} from '../../utils'; +import {AvatarHeaderHeight, ChatHeaderHeight, SCREEN_WIDTH} from '../../utils'; import {MainStack, MainStackParams} from './MainStackNavigator'; /** @@ -306,7 +306,10 @@ const MainStackScreen: React.FC = ({route}) => { ); diff --git a/src/screens/chat/ChatScreen.tsx b/src/screens/chat/ChatScreen.tsx index 8e6c1575..386feb03 100644 --- a/src/screens/chat/ChatScreen.tsx +++ b/src/screens/chat/ChatScreen.tsx @@ -1,8 +1,8 @@ import {useBottomTabBarHeight} from '@react-navigation/bottom-tabs'; -import {StackNavigationProp, useHeaderHeight} from '@react-navigation/stack'; +import {StackNavigationProp} from '@react-navigation/stack'; import React, {useContext} from 'react'; -import {StyleSheet, View} from 'react-native'; -import {useSelector} from 'react-redux'; +import {StyleSheet} from 'react-native'; +import {SafeAreaView} from 'react-native-safe-area-context'; import { Channel, Chat, @@ -10,8 +10,9 @@ import { MessageList, } from 'stream-chat-react-native'; import {ChatContext} from '../../App'; +import ChatHeader from '../../components/messages/ChatHeader'; import {MainStackParams} from '../../routes'; -import {RootState} from '../../store/rootReducer'; +import {isIPhoneX} from '../../utils'; type ChatScreenNavigationProp = StackNavigationProp; interface ChatScreenProps { @@ -22,33 +23,29 @@ interface ChatScreenProps { */ const ChatScreen: React.FC = () => { const {channel, chatClient} = useContext(ChatContext); - const headerHeight = useHeaderHeight(); const tabbarHeight = useBottomTabBarHeight(); - const {userId: loggedInUserId} = useSelector( - (state: RootState) => state.user.user, - ); - const otherMembers = channel - ? Object.values(channel.state.members).filter( - (member) => member.user?.id !== loggedInUserId, - ) - : []; - const member = otherMembers.length === 1 ? otherMembers[0] : undefined; return ( - + + - + {}} /> - + ); }; const styles = StyleSheet.create({ container: { backgroundColor: 'white', + flex: 1, }, }); diff --git a/src/types/types.ts b/src/types/types.ts index 582eefac..376c4be0 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -300,19 +300,18 @@ export type LocalMessageType = Record; export type LocalResponseType = Record; export type LocalReactionType = Record; export type LocalUserType = Record; +export type ChannelGroupedType = ChannelType< + LocalAttachmentType, + LocalChannelType, + LocalCommandType, + LocalEventType, + LocalMessageType, + LocalResponseType, + LocalUserType +>; export type ChatContextType = { - channel: - | ChannelType< - LocalAttachmentType, - LocalChannelType, - LocalCommandType, - LocalEventType, - LocalMessageType, - LocalResponseType, - LocalUserType - > - | undefined; + channel: ChannelGroupedType | undefined; setChannel: React.Dispatch< React.SetStateAction< | ChannelType< diff --git a/src/utils/layouts.ts b/src/utils/layouts.ts index e2f1f0b1..4d0d557d 100644 --- a/src/utils/layouts.ts +++ b/src/utils/layouts.ts @@ -31,6 +31,7 @@ export const StatusBarHeight = Platform.select({ }); export const AvatarHeaderHeight = (HeaderHeight + StatusBarHeight) * 1.3; +export const ChatHeaderHeight = (HeaderHeight + StatusBarHeight) * 1.1; /** * This is a function for normalizing the font size for different devices, based on iphone 8. diff --git a/src/utils/messages.ts b/src/utils/messages.ts index ae8e7cec..d63f2b7a 100644 --- a/src/utils/messages.ts +++ b/src/utils/messages.ts @@ -1,4 +1,6 @@ import moment from 'moment'; +import {RootState} from '../store/rootReducer'; +import {ChannelGroupedType} from '../types'; /** * Finds the difference in time in minutes @@ -57,3 +59,25 @@ export const isOnline = (lastActive: string | undefined) => { } return diff <= 15; }; + +/** + * Gets the other member in the channel. + * @param channel the current chat channel + * @param state the current redux state + * @returns other member or undefined + */ +export const getMember = ( + channel: ChannelGroupedType | undefined, + state: RootState, +) => { + if (!channel) { + return undefined; + } + const loggedInUserId = state.user.user.userId; + const otherMembers = channel + ? Object.values(channel.state.members).filter( + (member) => member.user?.id !== loggedInUserId, + ) + : []; + return otherMembers.length === 1 ? otherMembers[0] : undefined; +}; -- cgit v1.2.3-70-g09d2 From 98a31b59df5b51ea9488220d47bd7d60b3a268b9 Mon Sep 17 00:00:00 2001 From: Shravya Ramesh Date: Thu, 8 Apr 2021 17:11:28 -0700 Subject: minor styling and removed unused imports --- src/components/messages/ChannelPreview.tsx | 41 +++++++++++++++--------------- 1 file changed, 20 insertions(+), 21 deletions(-) (limited to 'src/components') diff --git a/src/components/messages/ChannelPreview.tsx b/src/components/messages/ChannelPreview.tsx index 8ec6060a..312f879a 100644 --- a/src/components/messages/ChannelPreview.tsx +++ b/src/components/messages/ChannelPreview.tsx @@ -2,10 +2,10 @@ import {useNavigation} from '@react-navigation/core'; import React, {useContext} from 'react'; import {Image, StyleSheet, Text, View} from 'react-native'; import {TouchableOpacity} from 'react-native-gesture-handler'; -import {useSelector, useStore} from 'react-redux'; +import {useStore} from 'react-redux'; +import {usernameRegex} from 'src/constants'; import {ChannelPreviewMessengerProps} from 'stream-chat-react-native'; import {ChatContext} from '../../App'; -import {RootState} from '../../store/rootReducer'; import { LocalAttachmentType, LocalChannelType, @@ -15,20 +15,18 @@ import { LocalReactionType, LocalUserType, } from '../../types'; -import {normalize, SCREEN_HEIGHT} from '../../utils'; +import {normalize, SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; import {getMember, isOnline} from '../../utils/messages'; -const ChannelPreview: React.FC< - ChannelPreviewMessengerProps< - LocalAttachmentType, - LocalChannelType, - LocalCommandType, - LocalEventType, - LocalMessageType, - LocalReactionType, - LocalUserType - > -> = (props) => { +const ChannelPreview: React.FC> = (props) => { const {setChannel} = useContext(ChatContext); const state = useStore().getState(); const navigation = useNavigation(); @@ -78,14 +76,14 @@ const styles = StyleSheet.create({ container: { flex: 1, flexDirection: 'row', - height: Math.round(SCREEN_HEIGHT / 10), + height: Math.round(SCREEN_HEIGHT / 9), + width: Math.round(SCREEN_WIDTH * 0.85), + alignSelf: 'center', alignItems: 'center', - paddingHorizontal: '8%', - paddingVertical: '5%', }, avatar: { - width: normalize(62), - height: normalize(62), + width: normalize(60), + height: normalize(60), borderRadius: normalize(62) / 2, }, online: { @@ -101,8 +99,7 @@ const styles = StyleSheet.create({ }, content: { flex: 1, - height: '100%', - justifyContent: 'space-between', + height: '60%', flexDirection: 'column', marginLeft: '5%', }, @@ -116,6 +113,7 @@ const styles = StyleSheet.create({ fontWeight: '500', fontSize: normalize(12), lineHeight: normalize(14), + paddingTop: '5%', }, unread: { fontWeight: '700', @@ -126,6 +124,7 @@ const styles = StyleSheet.create({ width: normalize(10), height: normalize(10), borderRadius: normalize(10) / 2, + marginLeft: '5%', }, }); -- cgit v1.2.3-70-g09d2