mediasfu-reactnative-expo
Version:
mediasfu-reactnative-expo – Expo-managed React Native WebRTC SDK for video conferencing, webinars, live streaming, broadcast, screen sharing, whiteboard, chat, recording, live subtitles, translation, and AI agent rooms on iOS, Android, and web. Prebuilt r
331 lines (327 loc) • 13.5 kB
JavaScript
import React, { useEffect, useState, useRef } from 'react';
import { Modal, View, Text, Pressable, StyleSheet, Dimensions, } from 'react-native';
import { FontAwesome5 } from '@expo/vector-icons';
import MessagePanel from './MessagePanel';
import { getModalPosition } from '../../methods/utils/getModalPosition';
import { sendMessage } from '../../methods/messageMethods/sendMessage';
import { getModalBodyTheme } from '../../components_modern/core/modalBodyTheme';
/**
* MessagesModal - Chat interface with tabbed group and direct messaging
*
* MessagesModal is a comprehensive React Native modal for real-time chat communication.
* It provides a tabbed interface with separate views for group messages and direct messages,
* message composition with input field, and automatic scrolling to new messages.
*
* **Key Features:**
* - Tabbed interface (Group Chat / Direct Messages)
* - Real-time message display with timestamps
* - Message composition with send button
* - Auto-scroll to latest messages
* - Direct message targeting to specific participants
* - Permission-based chat visibility controls
* - Responsive design with flexible positioning
* - Message history with sender identification
*
* **UI Customization:**
* This component can be replaced via `uiOverrides.messagesModalComponent` to
* provide a completely custom messages modal implementation.
*
* @component
* @param {MessagesModalOptions} props - Configuration options for the MessagesModal component
*
* @returns {JSX.Element} Rendered messages modal with chat interface
*
* @example
* // Basic usage - Display messages modal with group and direct messages
* import React, { useState } from 'react';
* import { MessagesModal } from 'mediasfu-reactnative-expo';
* import { Socket } from 'socket.io-client';
*
* function ChatControls() {
* const [isVisible, setIsVisible] = useState(false);
* const [messages, setMessages] = useState([
* { sender: 'Alice', message: 'Hello everyone!', timestamp: '10:01', group: true },
* { sender: 'Bob', message: 'Hey Alice!', timestamp: '10:02', receivers: ['Alice'], group: false },
* ]);
* const [startDM, setStartDM] = useState(false);
* const [dmDetails, setDmDetails] = useState(null);
*
* return (
* <>
* <Button title="Messages" onPress={() => setIsVisible(true)} />
* <MessagesModal
* isMessagesModalVisible={isVisible}
* onMessagesClose={() => setIsVisible(false)}
* messages={messages}
* eventType="conference"
* member="john_doe"
* islevel="1"
* coHostResponsibility={[{ name: 'chat', value: true }]}
* coHost="jane_doe"
* startDirectMessage={startDM}
* directMessageDetails={dmDetails}
* updateStartDirectMessage={setStartDM}
* updateDirectMessageDetails={setDmDetails}
* roomName="MainRoom"
* socket={socketInstance}
* chatSetting="allow"
* />
* </>
* );
* }
*
* @example
* // With custom styling and positioning
* <MessagesModal
* isMessagesModalVisible={showMessages}
* onMessagesClose={() => setShowMessages(false)}
* messages={chatMessages}
* position="bottomRight"
* backgroundColor="#1a1a2e"
* activeTabBackgroundColor="#0f3460"
* onSendMessagePress={async (options) => {
* console.log('Sending:', options.message);
* await sendMessage(options);
* setMessages([...messages, options.message]);
* }}
* eventType="webinar"
* member="presenter_1"
* islevel="2"
* coHostResponsibility={[{ name: 'chat', value: true }]}
* coHost="moderator_1"
* startDirectMessage={false}
* directMessageDetails={null}
* updateStartDirectMessage={(start) => console.log('Start DM:', start)}
* updateDirectMessageDetails={(participant) => console.log('DM to:', participant)}
* roomName="WebinarRoom"
* socket={socket}
* chatSetting="allow"
* />
*
* @example
* // Using uiOverrides for complete modal replacement
* import { MyCustomMessagesModal } from './MyCustomMessagesModal';
*
* const sessionConfig = {
* credentials: { apiKey: 'your-api-key' },
* uiOverrides: {
* messagesModalComponent: {
* component: MyCustomMessagesModal,
* injectedProps: {
* theme: 'minimal',
* showEmojis: true,
* },
* },
* },
* };
*
* // MyCustomMessagesModal.tsx
* export const MyCustomMessagesModal = (props: MessagesModalOptions & { theme: string; showEmojis: boolean }) => {
* return (
* <Modal visible={props.isMessagesModalVisible} onRequestClose={props.onMessagesClose}>
* <View style={{ backgroundColor: props.theme === 'minimal' ? '#fff' : '#000' }}>
* <Text>Chat Messages</Text>
* <ScrollView>
* {props.messages.map((msg, idx) => (
* <View key={idx}>
* <Text>{msg.sender}: {msg.message}</Text>
* <Text>{msg.timestamp}</Text>
* </View>
* ))}
* </ScrollView>
* {props.showEmojis && <Text>😀 😃 😄</Text>}
* </View>
* </Modal>
* );
* };
*/
const MessagesModal = ({ isMessagesModalVisible, onMessagesClose, messages, onSendMessagePress = sendMessage, position = 'topRight', backgroundColor = '#f5f5f5', isDarkMode, activeTabBackgroundColor = '#7AD2DCFF', eventType, member, islevel, coHostResponsibility, coHost, startDirectMessage, directMessageDetails, updateStartDirectMessage, updateDirectMessageDetails, showAlert, roomName, socket, chatSetting, style, renderContent, renderContainer, }) => {
const screenWidth = Dimensions.get('window').width;
let modalWidth = 0.8 * screenWidth;
if (modalWidth > 400) {
modalWidth = 400;
}
const [directMessages, setDirectMessages] = useState([]);
const [groupMessages, setGroupMessages] = useState([]);
const activeTab = useRef(eventType === 'webinar' || eventType === 'conference' ? 'direct' : 'group');
const [focusedInput, setFocusedInput] = useState(false);
const [reRender, setReRender] = useState(false);
/**
* Switches the active tab to 'direct'.
*/
const switchToDirectTab = () => {
activeTab.current = 'direct';
setReRender(!reRender);
};
/**
* Switches the active tab to 'group'.
*/
const switchToGroupTab = () => {
activeTab.current = 'group';
setReRender(!reRender);
};
useEffect(() => {
const chatValue = coHostResponsibility?.find((item) => item.name === 'chat')?.value;
const populateMessages = () => {
const directMsgs = messages.filter((message) => !message.group
&& (message.sender === member
|| message.receivers.includes(member)
|| islevel === '2'
|| (coHost === member && chatValue === true)));
setDirectMessages(directMsgs);
const groupMsgs = messages.filter((message) => message.group);
setGroupMessages(groupMsgs);
};
if (isMessagesModalVisible) {
populateMessages();
}
}, [
coHost,
coHostResponsibility,
isMessagesModalVisible,
islevel,
member,
messages,
]);
useEffect(() => {
if (startDirectMessage && directMessageDetails) {
if (eventType === 'webinar' || eventType === 'conference') {
activeTab.current = 'direct';
setFocusedInput(true);
}
}
else if (eventType === 'broadcast' || eventType === 'chat') {
activeTab.current = 'group';
}
}, [startDirectMessage, directMessageDetails, eventType]);
useEffect(() => {
// Force re-render when reRender state changes
}, [reRender]);
const dimensions = { width: modalWidth, height: 0 };
const theme = getModalBodyTheme(isDarkMode);
const shouldUseModernTheme = typeof isDarkMode === 'boolean';
const activeTabColor = shouldUseModernTheme ? theme.buttonBackgroundColor : activeTabBackgroundColor;
const defaultContent = (<>
<View style={styles.header}>
{eventType === 'webinar' || eventType === 'conference' ? (<View style={styles.tabsContainer}>
<Pressable onPress={switchToDirectTab} style={[
styles.tab,
activeTab.current === 'direct' && styles.activeTab,
activeTab.current === 'direct' && { backgroundColor: activeTabColor },
]}>
<Text style={[
styles.tabText,
activeTab.current === 'direct' && styles.activeTabText,
{ color: activeTab.current === 'direct' ? theme.buttonTextColor : theme.textColor },
]}>
Direct
</Text>
</Pressable>
<Pressable onPress={switchToGroupTab} style={[
styles.tab,
activeTab.current === 'group' && styles.activeTab,
activeTab.current === 'group' && { backgroundColor: activeTabColor },
]}>
<Text style={[
styles.tabText,
activeTab.current === 'group' && styles.activeTabText,
{ color: activeTab.current === 'group' ? theme.buttonTextColor : theme.textColor },
]}>
Group
</Text>
</Pressable>
</View>) : null}
{/* Close Button */}
<Pressable onPress={onMessagesClose} style={styles.closeButton}>
<FontAwesome5 name="times" size={24} color={theme.iconColor}/>
</Pressable>
</View>
<View style={[styles.separator, { backgroundColor: theme.dividerColor }]}/>
<View style={styles.modalBody}>
{activeTab.current === 'direct'
&& (eventType === 'webinar' || eventType === 'conference') && (<MessagePanel messages={directMessages} messagesLength={messages.length} type="direct" onSendMessagePress={onSendMessagePress} username={member} backgroundColor={backgroundColor} isDarkMode={isDarkMode} focusedInput={focusedInput} showAlert={showAlert} eventType={eventType} member={member} islevel={islevel} coHostResponsibility={coHostResponsibility} coHost={coHost} directMessageDetails={directMessageDetails} updateStartDirectMessage={updateStartDirectMessage} updateDirectMessageDetails={updateDirectMessageDetails} roomName={roomName} socket={socket} chatSetting={chatSetting} startDirectMessage={startDirectMessage}/>)}
{activeTab.current === 'group' && (<MessagePanel messages={groupMessages} messagesLength={messages.length} type="group" onSendMessagePress={onSendMessagePress} username={member} backgroundColor={backgroundColor} isDarkMode={isDarkMode} focusedInput={false} showAlert={showAlert} eventType={eventType} member={member} islevel={islevel} coHostResponsibility={coHostResponsibility} coHost={coHost} directMessageDetails={directMessageDetails} updateStartDirectMessage={updateStartDirectMessage} updateDirectMessageDetails={updateDirectMessageDetails} roomName={roomName} socket={socket} chatSetting={chatSetting} startDirectMessage={startDirectMessage}/>)}
</View>
</>);
const content = renderContent
? renderContent({ defaultContent, dimensions })
: defaultContent;
const defaultContainer = (<Modal animationType="fade" transparent visible={isMessagesModalVisible} onRequestClose={onMessagesClose}>
<View style={[styles.modalContainer, getModalPosition({ position })]}>
<View style={[styles.modalContent, { backgroundColor, width: modalWidth }, shouldUseModernTheme ? { borderColor: theme.borderColor, borderWidth: 1 } : null, style]}>
{content}
</View>
</View>
</Modal>);
return renderContainer
? renderContainer({ defaultContainer, dimensions })
: defaultContainer;
};
export default MessagesModal;
/**
* Stylesheet for the MessagesModal component.
*/
const styles = StyleSheet.create({
modalContainer: {
flex: 1,
justifyContent: 'flex-end',
alignItems: 'flex-end',
zIndex: 9,
elevation: 9,
},
modalContent: {
height: '75%',
backgroundColor: '#f5f5f5',
borderTopLeftRadius: 10,
borderTopRightRadius: 10,
padding: 10,
maxHeight: '75%',
maxWidth: '80%',
zIndex: 9,
elevation: 9, // For Android shadow
shadowColor: '#000', // For iOS shadow
shadowOffset: { width: 0, height: -2 },
shadowOpacity: 0.25,
shadowRadius: 4,
marginBottom: 10,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
tabsContainer: {
flexDirection: 'row',
},
tab: {
paddingHorizontal: 15,
paddingVertical: 5,
marginVertical: 10,
borderRadius: 4,
},
activeTab: {
// Additional styles for active tab if needed
},
tabText: {
fontWeight: 'bold',
fontSize: 16,
color: '#000000',
},
activeTabText: {
color: '#ffffff',
backgroundColor: '#7AD2DCFF',
borderRadius: 4,
},
separator: {
height: 1,
backgroundColor: 'black',
marginVertical: 5,
},
closeButton: {
padding: 5,
},
modalBody: {
flex: 1,
},
});
//# sourceMappingURL=MessagesModal.js.map