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

334 lines (331 loc) 13.3 kB
// ParticipantsModal.tsx import React, { useEffect, useState } from 'react'; import { Modal, View, Text, Pressable, StyleSheet, ScrollView, TextInput, Dimensions, } from 'react-native'; import { FontAwesome } from '@expo/vector-icons'; import { getModalPosition } from '../../methods/utils/getModalPosition'; import ParticipantList from './ParticipantList'; import ParticipantListOthers from './ParticipantListOthers'; import { muteParticipants } from '../../methods/participantsMethods/muteParticipants'; import { messageParticipants } from '../../methods/participantsMethods/messageParticipants'; import { removeParticipants } from '../../methods/participantsMethods/removeParticipants'; /** * ParticipantsModal - Participant list with search, filtering, and management actions * * ParticipantsModal is a comprehensive React Native modal for viewing and managing * session participants. It provides search/filter functionality, participant lists * (segmented by role/type), and action buttons for muting, messaging, or removing * participants based on user permissions. * * **Key Features:** * - Real-time participant list with count display * - Search/filter functionality for finding participants * - Segmented lists (main participants vs. others) * - Permission-based actions (mute, message, remove) * - Role-based visibility (host, co-host, participant) * - Custom renderers for participant list items * - Responsive design with flexible positioning * * **UI Customization:** * This component can be replaced via `uiOverrides.participantsModalComponent` to * provide a completely custom participants modal implementation. * * @component * @param {ParticipantsModalOptions} props - Configuration options for the ParticipantsModal component * * @returns {JSX.Element} Rendered participants modal with list and management actions * * @example * // Basic usage - Display participants modal with default settings * import React, { useState } from 'react'; * import { ParticipantsModal } from 'mediasfu-reactnative-expo'; * import { Socket } from 'socket.io-client'; * * function MeetingControls() { * const [isModalVisible, setIsModalVisible] = useState(false); * const [participants, setParticipants] = useState([ * { name: 'Alice', id: '1', islevel: '1', muted: false }, * { name: 'Bob', id: '2', islevel: '1', muted: true }, * ]); * * const participantsParameters = { * position: 'topRight', * backgroundColor: '#83c0e9', * coHostResponsibility: [{ name: 'participants', value: true }], * coHost: 'JaneDoe', * member: 'JohnDoe', * islevel: '2', * participants, * eventType: 'conference', * filteredParticipants: participants, * socket: socketInstance, * roomName: 'MainRoom', * updateIsMessagesModalVisible: (visible) => console.log('Messages:', visible), * updateDirectMessageDetails: (participant) => console.log('DM:', participant), * updateStartDirectMessage: (start) => console.log('Start DM:', start), * updateParticipants: setParticipants, * getUpdatedAllParams: () => participantsParameters, * }; * * return ( * <> * <Button title="Participants" onPress={() => setIsModalVisible(true)} /> * <ParticipantsModal * isParticipantsModalVisible={isModalVisible} * onParticipantsClose={() => setIsModalVisible(false)} * onParticipantsFilterChange={(filter) => console.log('Filter:', filter)} * participantsCounter={participants.length} * parameters={participantsParameters} * /> * </> * ); * } * * @example * // With custom action handlers and positioning * <ParticipantsModal * isParticipantsModalVisible={showParticipants} * onParticipantsClose={() => setShowParticipants(false)} * onParticipantsFilterChange={(filter) => handleFilterChange(filter)} * participantsCounter={filteredParticipants.length} * onMuteParticipants={async (options) => { * console.log('Custom mute logic'); * await muteParticipants(options); * }} * onMessageParticipants={(options) => { * console.log('Messaging participant:', options.participant.name); * messageParticipants(options); * }} * onRemoveParticipants={async (options) => { * if (confirm('Remove participant?')) { * await removeParticipants(options); * } * }} * parameters={{ * ...participantsParameters, * position: 'center', * backgroundColor: '#1a1a2e', * }} * backgroundColor="#1a1a2e" * position="center" * /> * * @example * // Using uiOverrides for complete modal replacement * import { MyCustomParticipantsModal } from './MyCustomParticipantsModal'; * * const sessionConfig = { * credentials: { apiKey: 'your-api-key' }, * uiOverrides: { * participantsModalComponent: { * component: MyCustomParticipantsModal, * injectedProps: { * theme: 'compact', * showAvatars: true, * }, * }, * }, * }; * * // MyCustomParticipantsModal.tsx * export const MyCustomParticipantsModal = (props: ParticipantsModalOptions & { theme: string; showAvatars: boolean }) => { * return ( * <Modal visible={props.isParticipantsModalVisible} onRequestClose={props.onParticipantsClose}> * <View> * <Text>Participants ({props.participantsCounter})</Text> * {props.parameters.participants.map(p => ( * <View key={p.id}> * {props.showAvatars && <Image source={{ uri: p.avatar }} />} * <Text>{p.name}</Text> * </View> * ))} * </View> * </Modal> * ); * }; */ const ParticipantsModal = ({ isParticipantsModalVisible, onParticipantsClose, onParticipantsFilterChange, participantsCounter, onMuteParticipants = muteParticipants, onMessageParticipants = messageParticipants, onRemoveParticipants = removeParticipants, RenderParticipantList = ParticipantList, RenderParticipantListOthers = ParticipantListOthers, position = 'topRight', backgroundColor = '#83c0e9', isDarkMode, parameters, style, renderContent, renderContainer, }) => { const { coHostResponsibility, coHost, member, islevel, showAlert, participants, roomName, eventType, socket, updateIsMessagesModalVisible, updateDirectMessageDetails, updateStartDirectMessage, updateParticipants, } = parameters; const [participantList, setParticipantList] = useState(participants); const [participantsCounter_s, setParticipantsCounter_s] = useState(participantsCounter); const [filterText, setFilterText] = useState(''); const screenWidth = Dimensions.get('window').width; let modalWidth = 0.8 * screenWidth; if (modalWidth > 400) { modalWidth = 400; } let participantsValue = false; try { participantsValue = coHostResponsibility?.find((item) => item.name === 'participants')?.value ?? false; } catch { // Default to false if not found } useEffect(() => { if (!isParticipantsModalVisible) { return; } const updatedParams = parameters.getUpdatedAllParams(); setParticipantList(updatedParams.filteredParticipants); setParticipantsCounter_s(updatedParams.filteredParticipants.length); }, [isParticipantsModalVisible, participants, parameters]); if (!isParticipantsModalVisible) { return null; } const dimensions = { width: modalWidth, height: 0 }; const themed = typeof isDarkMode === 'boolean'; const textColor = themed ? (isDarkMode ? '#f8fafc' : '#0f172a') : 'black'; const mutedTextColor = themed ? (isDarkMode ? '#cbd5e1' : '#475569') : 'gray'; const borderColor = themed ? (isDarkMode ? 'rgba(226, 232, 240, 0.18)' : 'rgba(71, 85, 105, 0.22)') : '#000000'; const inputBackgroundColor = themed ? (isDarkMode ? '#1e293b' : '#ffffff') : 'white'; const inputBorderColor = themed ? (isDarkMode ? 'rgba(226, 232, 240, 0.22)' : 'rgba(71, 85, 105, 0.28)') : 'gray'; const badgeBackgroundColor = themed ? (isDarkMode ? '#38bdf8' : '#2563eb') : '#fff'; const badgeTextColor = themed ? '#ffffff' : '#000'; const defaultContent = (<ScrollView style={styles.scrollView}> <View style={styles.modalHeader}> <Text style={[styles.modalTitle, { color: textColor }]}> Participants {' '} <Text style={[styles.badge, { backgroundColor: badgeBackgroundColor, color: badgeTextColor }]}>{participantsCounter_s}</Text> </Text> <Pressable onPress={onParticipantsClose} style={styles.closeButton}> <FontAwesome name="times" size={24} color={textColor}/> </Pressable> </View> <View style={[styles.separator, { backgroundColor: borderColor }]}/> <View style={styles.modalBody}> {/* Search Input */} <View style={styles.formGroup}> <TextInput style={[styles.input, { color: textColor, borderColor: inputBorderColor, backgroundColor: inputBackgroundColor }]} placeholder="Search ..." placeholderTextColor={mutedTextColor} value={filterText} onChangeText={(text) => { setFilterText(text); onParticipantsFilterChange(text); }}/> </View> {/* Participant List */} {(participantList && islevel === '2') || (coHost === member && participantsValue === true) ? (<RenderParticipantList participants={participantList} isBroadcast={eventType === 'broadcast'} onMuteParticipants={onMuteParticipants} onMessageParticipants={onMessageParticipants} onRemoveParticipants={onRemoveParticipants} socket={socket} coHostResponsibility={coHostResponsibility} member={member} islevel={islevel} showAlert={showAlert} coHost={coHost} roomName={roomName} updateIsMessagesModalVisible={updateIsMessagesModalVisible} updateDirectMessageDetails={updateDirectMessageDetails} updateStartDirectMessage={updateStartDirectMessage} updateParticipants={updateParticipants} isDarkMode={isDarkMode}/>) : participantList ? (<RenderParticipantListOthers participants={participantList} coHost={coHost} member={member} isDarkMode={isDarkMode}/>) : (<Text style={[styles.noParticipantsText, { color: mutedTextColor }]}>No participants</Text>)} </View> </ScrollView>); const content = renderContent ? renderContent({ defaultContent, dimensions }) : defaultContent; const defaultContainer = (<Modal transparent animationType="slide" visible={isParticipantsModalVisible} onRequestClose={onParticipantsClose}> <View style={[styles.modalContainer, getModalPosition({ position })]}> <View style={[styles.modalContent, { backgroundColor, width: modalWidth }, style]}> {content} </View> </View> </Modal>); return renderContainer ? renderContainer({ defaultContainer, dimensions }) : defaultContainer; }; export default ParticipantsModal; /** * Stylesheet for the ParticipantsModal component. */ const styles = StyleSheet.create({ modalContainer: { flex: 1, justifyContent: 'flex-end', alignItems: 'flex-end', zIndex: 9, elevation: 9, }, modalContent: { height: '65%', backgroundColor: '#83c0e9', borderRadius: 10, padding: 15, maxHeight: '65%', maxWidth: '70%', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.25, shadowRadius: 4, elevation: 9, zIndex: 9, }, modalHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, modalTitle: { fontSize: 18, fontWeight: 'bold', color: 'black', flexDirection: 'row', alignItems: 'center', }, badge: { backgroundColor: '#fff', color: '#000', borderRadius: 12, paddingHorizontal: 5, paddingVertical: 2, marginLeft: 5, fontSize: 14, }, closeButton: { padding: 5, }, separator: { height: 1, backgroundColor: '#000000', marginVertical: 10, }, modalBody: { flex: 1, }, formGroup: { marginBottom: 10, }, input: { fontSize: 14, paddingVertical: 8, paddingHorizontal: 8, borderWidth: 1, borderColor: 'gray', borderRadius: 4, color: 'black', paddingRight: 20, backgroundColor: 'white', }, scrollView: { flexGrow: 1, }, waitingItem: { flexDirection: 'row', alignItems: 'center', marginVertical: 5, paddingVertical: 10, borderBottomWidth: 1, borderBottomColor: '#ddd', }, participantName: { flex: 5, }, participantText: { fontSize: 16, color: 'black', }, actionButtons: { flex: 2, flexDirection: 'row', justifyContent: 'space-around', alignItems: 'center', }, acceptButton: { padding: 5, }, rejectButton: { padding: 5, }, noParticipantsText: { textAlign: 'center', color: 'gray', fontSize: 16, marginTop: 20, }, }); //# sourceMappingURL=ParticipantsModal.js.map