aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIvan Chen <ivan@tagg.id>2021-04-08 18:42:28 -0400
committerIvan Chen <ivan@tagg.id>2021-04-08 18:42:28 -0400
commitfab86f9b874524a4beabb3c45a9e59e8b00ca495 (patch)
tree8ab36bc222ac9f26c7c6ec57ac0bf8dc1bf6ed57 /src
parentbb16e95e15f6ea9b8941cae764570bdf7c0fdb59 (diff)
added chat header, created isMember util, fixed KB padding issue
Diffstat (limited to 'src')
-rw-r--r--src/App.tsx15
-rw-r--r--src/components/messages/ChannelPreview.tsx25
-rw-r--r--src/components/messages/ChatHeader.tsx84
-rw-r--r--src/routes/main/MainStackScreen.tsx7
-rw-r--r--src/screens/chat/ChatScreen.tsx31
-rw-r--r--src/types/types.ts21
-rw-r--r--src/utils/layouts.ts1
-rw-r--r--src/utils/messages.ts24
8 files changed, 151 insertions, 57 deletions
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<ChannelGroupedType>();
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<
<Image
style={styles.avatar}
source={
- otherMembers.length === 1
- ? {uri: member?.user?.thumbnail_url}
+ member
+ ? {uri: member.user?.thumbnail_url}
: require('../../assets/images/avatar-placeholder.png')
}
/>
@@ -71,7 +64,9 @@ const ChannelPreview: React.FC<
<Text
style={[styles.lastMessage, unread ? styles.unread : {}]}
numberOfLines={1}>
- {channel.state.messages[channel.state.messages.length - 1].text}
+ {channel.state.messages.length > 0
+ ? channel.state.messages[channel.state.messages.length - 1].text
+ : ''}
</Text>
</View>
{unread && <View style={styles.purpleDot} />}
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<ChatHeaderProps> = () => {
+ 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 (
+ <View style={styles.container}>
+ <View>
+ <Image
+ style={styles.avatar}
+ source={
+ member
+ ? {uri: member.user?.thumbnail_url}
+ : require('../../assets/images/avatar-placeholder.png')
+ }
+ />
+ {online && <View style={styles.online} />}
+ </View>
+ <View style={styles.content}>
+ <Text style={styles.name} numberOfLines={1}>
+ {member?.user?.first_name} {member?.user?.last_name}
+ </Text>
+ <Text style={styles.lastSeen}>{lastSeen}</Text>
+ </View>
+ </View>
+ );
+};
+
+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<MainStackProps> = ({route}) => {
<MainStack.Screen
name="Chat"
component={ChatScreen}
- options={{headerShown: true}}
+ options={{
+ ...headerBarOptions('black', ''),
+ headerStyle: {height: ChatHeaderHeight},
+ }}
/>
</MainStack.Navigator>
);
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<MainStackParams, 'Chat'>;
interface ChatScreenProps {
@@ -22,33 +23,29 @@ interface ChatScreenProps {
*/
const ChatScreen: React.FC<ChatScreenProps> = () => {
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 (
- <View style={[styles.container, {paddingBottom: tabbarHeight}]}>
+ <SafeAreaView
+ style={[
+ styles.container,
+ {paddingBottom: isIPhoneX() ? tabbarHeight + 20 : tabbarHeight + 50},
+ ]}>
+ <ChatHeader />
<Chat client={chatClient}>
- <Channel channel={channel} keyboardVerticalOffset={headerHeight}>
+ <Channel channel={channel} keyboardVerticalOffset={0}>
<MessageList onThreadSelect={() => {}} />
<MessageInput />
</Channel>
</Chat>
- </View>
+ </SafeAreaView>
);
};
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<string, unknown>;
export type LocalResponseType = Record<string, unknown>;
export type LocalReactionType = Record<string, unknown>;
export type LocalUserType = Record<string, unknown>;
+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;
+};