UNPKG

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
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