import React, { useCallback, useEffect, useRef, useState } from 'react';
import { StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useFocusEffect } from '@react-navigation/native';
import type { RouteProp } from '@react-navigation/native';
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
import type { Channel as StreamChatChannel } from 'stream-chat';
import {
Channel,
MessageComposer,
MessageList,
UserAdd,
WithComponents,
useTheme,
} from 'stream-chat-react-native';
import { NewDirectMessagingSendButton } from '../components/NewDirectMessagingSendButton';
import { RoundButton } from '../components/RoundButton';
import { ScreenHeader } from '../components/ScreenHeader';
import { SelectedUserTag } from '../components/UserSearch/SelectedUserTag';
import { UserSearchResults } from '../components/UserSearch/UserSearchResults';
import { useAppContext } from '../context/AppContext';
import { useUserSearchContext } from '../context/UserSearchContext';
import { Group } from '../icons/Group';
import { User } from '../icons/User';
import { useLegacyColors } from '../theme/useLegacyColors';
import type { StackNavigatorParamList } from '../types';
const styles = StyleSheet.create({
container: {
flex: 1,
},
createGroupButtonContainer: {
alignItems: 'center',
flexDirection: 'row',
paddingHorizontal: 8,
paddingVertical: 16,
},
createGroupButtonText: {
fontSize: 14,
fontWeight: '700',
paddingLeft: 8,
},
emptyMessageContainer: {
alignItems: 'center',
flex: 1,
justifyContent: 'center',
},
inputBox: {
flex: 1,
fontSize: 14,
includeFontPadding: false,
padding: 0,
paddingRight: 16,
paddingTop: 0,
textAlignVertical: 'center',
},
inputBoxContainer: {
flexDirection: 'row',
},
noChats: { fontSize: 12 },
searchContainer: {
borderBottomWidth: 1,
flexDirection: 'row',
},
searchContainerLeft: {
fontSize: 12,
paddingHorizontal: 16,
paddingVertical: 20,
textAlignVertical: 'center',
},
searchContainerMiddle: {
flex: 1,
justifyContent: 'center',
},
searchContainerRight: {
justifyContent: 'flex-end',
paddingBottom: 16,
paddingRight: 16,
},
selectedUsersContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
},
});
export const EmptyMessagesIndicator = () => {
const { grey } = useLegacyColors();
return (
<View style={styles.emptyMessageContainer}>
<Text
style={[
styles.noChats,
{
color: grey,
},
]}
>
No chats here yet...
</Text>
</View>
);
};
export type NewDirectMessagingScreenNavigationProp = NativeStackNavigationProp<
StackNavigatorParamList,
'NewDirectMessagingScreen'
>;
export type NewDirectMessagingScreenProps = {
navigation: NewDirectMessagingScreenNavigationProp;
route: RouteProp<StackNavigatorParamList, 'NewDirectMessagingScreen'>;
};
export const NewDirectMessagingScreen: React.FC<NewDirectMessagingScreenProps> = ({
navigation,
route,
}) => {
const {
theme: { semantics },
} = useTheme();
const { accent_blue, black, grey, white } = useLegacyColors();
const { chatClient } = useAppContext();
const {
onChangeSearchText,
onFocusInput,
reset,
results,
searchText,
selectedUserIds,
selectedUsers,
toggleUser,
} = useUserSearchContext();
const messageInputRef = useRef<TextInput | null>(null);
const searchInputRef = useRef<TextInput>(null);
const currentChannel = useRef<StreamChatChannel>(undefined);
const isDraft = useRef(true);
const initialUserIdRef = useRef<string>(undefined);
const [focusOnMessageInput, setFocusOnMessageInput] = useState(false);
const [focusOnSearchInput, setFocusOnSearchInput] = useState(true);
useEffect(() => {
const initialUser = route.params?.initialUser;
if (!initialUser || initialUserIdRef.current === initialUser.id) {
return;
}
initialUserIdRef.current = initialUser.id;
reset();
toggleUser(initialUser);
}, [route.params?.initialUser, reset, toggleUser]);
const selectedUsersLength = selectedUsers.length;
useFocusEffect(
React.useCallback(() => {
if (selectedUsersLength === 0) {
currentChannel.current = undefined;
isDraft.current = true;
setFocusOnMessageInput(false);
setFocusOnSearchInput(true);
}
}, [selectedUsersLength]),
);
useEffect(() => {
const initChannel = async () => {
if (!chatClient?.user?.id) {
return;
}
if (selectedUsersLength === 0) {
currentChannel.current = undefined;
isDraft.current = true;
setFocusOnMessageInput(false);
setFocusOnSearchInput(true);
return;
}
setFocusOnSearchInput(false);
const members = [chatClient.user.id, ...selectedUserIds];
const channels = await chatClient.queryChannels({
members,
});
if (channels.length === 1) {
currentChannel.current = channels[0];
isDraft.current = false;
} else {
isDraft.current = true;
const channel = chatClient.channel('messaging', {
members,
});
channel.initialized = true;
currentChannel.current = channel;
}
if (messageInputRef.current) {
messageInputRef.current.focus();
}
setFocusOnMessageInput(true);
};
initChannel();
}, [chatClient, selectedUserIds, selectedUsersLength]);
const onBackPress = useCallback(() => {
reset();
if (!navigation.canGoBack()) {
navigation.reset({ index: 0, routes: [{ name: 'MessagingScreen' }] });
return;
}
navigation.goBack();
}, [navigation, reset]);
const renderUserSearch = ({ inSafeArea }: { inSafeArea: boolean }) => (
<View style={[{ backgroundColor: white }, focusOnSearchInput ? styles.container : undefined]}>
<ScreenHeader inSafeArea={inSafeArea} onBack={onBackPress} titleText='New Chat' />
<TouchableOpacity
activeOpacity={1}
onPress={() => {
setFocusOnMessageInput(false);
setFocusOnSearchInput(true);
if (searchInputRef.current) {
searchInputRef.current.focus();
}
}}
style={[
styles.searchContainer,
{
backgroundColor: white,
borderBottomColor: semantics.borderCoreDefault,
},
]}
>
<Text
style={[
styles.searchContainerLeft,
{
color: grey,
},
]}
>
TO:
</Text>
<View style={styles.searchContainerMiddle}>
<View style={styles.selectedUsersContainer}>
{selectedUsers.map((tag, index) => {
const tagProps = {
disabled: !focusOnSearchInput,
index,
onPress: () => {
toggleUser(tag);
},
tag,
};
return <SelectedUserTag key={index} {...tagProps} />;
})}
</View>
{focusOnSearchInput && (
<View style={styles.inputBoxContainer}>
<TextInput
onChangeText={onChangeSearchText}
onFocus={onFocusInput}
placeholder='Type a name'
placeholderTextColor={grey}
ref={searchInputRef}
style={[
styles.inputBox,
{
color: black,
paddingBottom: selectedUsers.length ? 16 : 0,
},
]}
value={searchText}
/>
</View>
)}
</View>
<View style={styles.searchContainerRight}>
{selectedUsers.length === 0 ? (
<User pathFill={grey} />
) : (
<UserAdd pathFill={grey} height={20} width={20} />
)}
</View>
</TouchableOpacity>
{focusOnSearchInput && !searchText && selectedUsers.length === 0 && (
<TouchableOpacity
onPress={() => {
navigation.push('NewGroupChannelAddMemberScreen');
}}
style={styles.createGroupButtonContainer}
>
<RoundButton>
<Group pathFill={accent_blue} />
</RoundButton>
<Text
style={[
styles.createGroupButtonText,
{
color: black,
},
]}
>
Create a Group
</Text>
</TouchableOpacity>
)}
{results && focusOnSearchInput && (
<UserSearchResults
toggleSelectedUser={(user) => {
setFocusOnSearchInput(false);
toggleUser(user);
}}
/>
)}
</View>
);
if (!chatClient) {
return null;
}
if (!currentChannel.current) {
return renderUserSearch({ inSafeArea: false });
}
return (
<SafeAreaView
style={[
styles.container,
{
backgroundColor: white,
},
]}
>
<Channel
additionalTextInputProps={{
onFocus: () => {
setFocusOnMessageInput(true);
setFocusOnSearchInput(false);
if (messageInputRef.current) {
messageInputRef.current.focus();
}
},
}}
audioRecordingEnabled={true}
channel={currentChannel.current}
enforceUniqueReaction
keyboardVerticalOffset={0}
overrideOwnCapabilities={{ sendMessage: true }}
setInputRef={(ref) => (messageInputRef.current = ref)}
>
{renderUserSearch({ inSafeArea: true })}
{results && results.length >= 0 && !focusOnSearchInput && focusOnMessageInput && (
<MessageList />
)}
<WithComponents overrides={{ SendButton: NewDirectMessagingSendButton }}>
<MessageComposer />
</WithComponents>
</Channel>
</SafeAreaView>
);
};