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

450 lines (449 loc) 16.7 kB
import React, { useState, useEffect } from 'react'; import { Modal, View, Text, Pressable, StyleSheet, Dimensions, Switch, TextInput, ScrollView, } from 'react-native'; import { FontAwesome } from "@expo/vector-icons"; import RNPickerSelect from 'react-native-picker-select'; import { getModalPosition } from '../../methods/utils/getModalPosition'; import { modifyCoHostSettings } from '../../methods/coHostMethods/modifyCoHostSettings'; import { createThemedPickerSelectStyles, getModalBodyTheme } from '../../components_modern/core/modalBodyTheme'; /** * CoHostModal - Co-host assignment and responsibility management interface * * CoHostModal is a React Native component that provides host users with controls * to assign a co-host from participant list and configure their responsibilities * (media management, participant management, settings access). Changes are * broadcast in real-time via Socket.io. * * **Key Features:** * - Co-host participant selection from dropdown * - Granular responsibility toggles (manage media, manage participants, manage settings) * - Real-time updates via Socket.io * - Current co-host display * - Validation and error alerts * - Position-configurable modal (4 corners) * - Scrollable responsibility list * * **UI Customization:** * This component can be replaced via `uiOverrides.coHostModal` to * provide a completely custom co-host management interface. * * @component * @param {CoHostModalOptions} props - Configuration options * * @returns {JSX.Element} Rendered co-host modal * * @example * ```tsx * // Basic usage with participant selection * import React, { useState } from 'react'; * import { CoHostModal } from 'mediasfu-reactnative-expo'; * import { io } from 'socket.io-client'; * * const socket = io('https://your-server.com'); * const [showCoHost, setShowCoHost] = useState(false); * * const participants = [ * { name: 'John Doe', islevel: '1', id: '123' }, * { name: 'Jane Smith', islevel: '2', id: '456' }, * ]; * * const responsibilities = [ * { name: 'manageParticipants', value: true, dedicated: false }, * { name: 'manageMedia', value: false, dedicated: true }, * { name: 'manageSettings', value: true, dedicated: false }, * ]; * * return ( * <CoHostModal * isCoHostModalVisible={showCoHost} * onCoHostClose={() => setShowCoHost(false)} * currentCohost="John Doe" * participants={participants} * coHostResponsibility={responsibilities} * roomName="meeting-room-123" * socket={socket} * updateCoHost={(name) => console.log('New co-host:', name)} * updateCoHostResponsibility={(resp) => console.log('Updated:', resp)} * updateIsCoHostModalVisible={setShowCoHost} * showAlert={({ message, type }) => alert(message)} * /> * ); * ``` * * @example * ```tsx * // With custom positioning and alert handling * const handleModifySettings = (settings: ModifyCoHostSettingsOptions) => { * modifyCoHostSettings(settings); * console.log('Co-host settings applied'); * }; * * return ( * <CoHostModal * isCoHostModalVisible={isVisible} * onCoHostClose={handleClose} * onModifyEventSettings={handleModifySettings} * currentCohost={currentCoHost} * participants={participantList} * coHostResponsibility={coHostResponsibilities} * roomName={roomId} * socket={socketInstance} * updateCoHost={setCurrentCoHost} * updateCoHostResponsibility={setCoHostResponsibilities} * updateIsCoHostModalVisible={setIsVisible} * position="bottomRight" * backgroundColor="#2c3e50" * showAlert={showCustomAlert} * /> * ); * ``` * * @example * ```tsx * // Using custom UI via uiOverrides * const config = { * uiOverrides: { * coHostModal: { * component: MyCustomCoHostManager, * injectedProps: { * theme: 'dark', * allowMultipleCoHosts: false, * }, * }, * }, * }; * * return <MyMeetingComponent config={config} />; * ``` */ const CoHostModal = ({ isCoHostModalVisible, onCoHostClose, onModifyEventSettings = modifyCoHostSettings, currentCohost = 'No coHost', participants, coHostResponsibility, position = 'topRight', backgroundColor = '#83c0e9', isDarkMode, roomName, showAlert, updateCoHostResponsibility, updateCoHost, updateIsCoHostModalVisible, socket, style, renderContent, renderContainer, }) => { const [selectedCohost, setSelectedCohost] = useState(currentCohost); const [coHostResponsibilityCopy, setCoHostResponsibilityCopy] = useState([...coHostResponsibility]); const [coHostResponsibilityCopyAlt, setCoHostResponsibilityCopyAlt] = useState([...coHostResponsibility]); const initialResponsibilities = coHostResponsibilityCopyAlt.reduce((acc, item) => { const str2 = item.name.charAt(0).toUpperCase() + item.name.slice(1); const keyed = `manage${str2}`; acc[keyed] = item.value; acc[`dedicateTo${keyed}`] = item.dedicated; return acc; }, {}); const [responsibilities, setResponsibilities] = useState(initialResponsibilities); const responsibilityItems = [ { name: 'manageParticipants', label: 'Manage Participants' }, { name: 'manageMedia', label: 'Manage Media' }, { name: 'manageWaiting', label: 'Manage Waiting Room' }, { name: 'manageChat', label: 'Manage Chat' }, ]; // Filter out the current co-host from the list of participants and any participant with islevel '2' const filteredParticipants = participants?.filter((participant) => participant.name !== currentCohost && participant.islevel !== '2'); const handleToggleSwitch = (responsibility) => { setResponsibilities((prevResponsibilities) => ({ ...prevResponsibilities, [responsibility]: !prevResponsibilities[responsibility], })); // Update the coHostResponsibilityCopy if (responsibility.startsWith('dedicateTo')) { const responsibilityName = responsibility .replace('dedicateTomanage', '') .toLowerCase(); const responsibilityItem = coHostResponsibilityCopy.find((item) => item.name === responsibilityName); if (responsibilityItem) { responsibilityItem.dedicated = !responsibilityItem.dedicated; setCoHostResponsibilityCopy([...coHostResponsibilityCopy]); } } else if (responsibility.startsWith('manage')) { const responsibilityName = responsibility .replace('manage', '') .toLowerCase(); const responsibilityItem = coHostResponsibilityCopy.find((item) => item.name === responsibilityName); if (responsibilityItem) { responsibilityItem.value = !responsibilityItem.value; setCoHostResponsibilityCopy([...coHostResponsibilityCopy]); } } }; const screenWidth = Dimensions.get('window').width; let modalWidth = 0.8 * screenWidth; if (modalWidth > 400) { modalWidth = 400; } useEffect(() => { const populateResponsibilities = () => { setCoHostResponsibilityCopyAlt([...coHostResponsibility]); setCoHostResponsibilityCopy([...coHostResponsibility]); const responsibilities = coHostResponsibilityCopyAlt.reduce((acc, item) => { const str2 = item.name.charAt(0).toUpperCase() + item.name.slice(1); const keyed = `manage${str2}`; acc[keyed] = item.value; acc[`dedicateTo${keyed}`] = item.dedicated; return acc; }, {}); setResponsibilities(responsibilities); }; if (isCoHostModalVisible) { populateResponsibilities(); } }, [isCoHostModalVisible, coHostResponsibility]); const handleSave = () => { onModifyEventSettings({ roomName: roomName, showAlert: showAlert, selectedParticipant: selectedCohost, coHost: currentCohost, coHostResponsibility: coHostResponsibilityCopy, updateCoHostResponsibility: updateCoHostResponsibility, updateCoHost: updateCoHost, updateIsCoHostModalVisible: updateIsCoHostModalVisible, socket: socket, }); }; const dimensions = { width: modalWidth, height: 0 }; const theme = getModalBodyTheme(isDarkMode); const shouldUseModernTheme = typeof isDarkMode === 'boolean'; const themedPickerSelectStyles = createThemedPickerSelectStyles(theme); const defaultContent = (<> <ScrollView> <View style={styles.modalHeader}> <Text style={[styles.modalTitle, { color: theme.textColor }]}>Manage Co-Host</Text> <Pressable onPress={onCoHostClose} style={styles.btnCloseSettings}> <FontAwesome name="times" style={[styles.icon, { color: theme.iconColor }]}/> </Pressable> </View> <View style={[styles.hr, { backgroundColor: theme.dividerColor }]}/> <View style={styles.modalBody}> <View style={styles.formGroup}> <Text style={[styles.label, { fontWeight: 'bold', color: theme.textColor }]}> Current Co-host: </Text> <TextInput style={[ styles.input, styles.disabledInput, { backgroundColor: theme.inputBackgroundColor, borderColor: theme.borderColor, color: theme.inputTextColor }, ]} value={currentCohost} editable={false}/> </View> <View style={[styles.sep, { backgroundColor: theme.dividerColor }]}/> <View style={styles.formGroup}> <Text style={[styles.label, { fontWeight: 'bold', color: theme.textColor }]}> Select New Co-host: </Text> <RNPickerSelect style={themedPickerSelectStyles} value={selectedCohost} onValueChange={(value) => setSelectedCohost(value)} items={filteredParticipants ? filteredParticipants.map((participant) => ({ label: participant.name, value: participant.name, })) : []} placeholder={{ label: 'Select a participant', value: '' }} useNativeAndroidPickerStyle={false}/> </View> <View style={[styles.sep, { backgroundColor: theme.dividerColor }]}/> <View style={styles.row}> <View style={styles.col5}> <Text style={[styles.label, { fontWeight: 'bold', color: theme.textColor }]}> Responsibility </Text> </View> <View style={styles.col3}> <Text style={[styles.label, { fontWeight: 'bold', color: theme.textColor }]}> Select </Text> </View> <View style={styles.col4}> <Text style={[styles.label, { fontWeight: 'bold', color: theme.textColor }]}> Dedicated </Text> </View> </View> {responsibilityItems.map((item) => (<View style={styles.row} key={item.name}> <View style={styles.col5}> <Text style={[styles.label, { color: theme.textColor }]}>{item.label}</Text> </View> <View style={styles.col3}> <Switch trackColor={{ false: theme.borderColor, true: theme.accentColor }} thumbColor={responsibilities[item.name] ? theme.successColor : theme.mutedTextColor} ios_backgroundColor="#3e3e3e" onValueChange={() => handleToggleSwitch(item.name)} value={responsibilities[item.name]}/> </View> <View style={styles.col4}> <Switch trackColor={{ false: theme.borderColor, true: theme.accentColor }} thumbColor={responsibilities[item.name] && responsibilities[`dedicateTo${item.name}`] ? theme.successColor : theme.mutedTextColor} ios_backgroundColor="#3e3e3e" onValueChange={() => handleToggleSwitch(`dedicateTo${item.name}`)} value={responsibilities[`dedicateTo${item.name}`] && responsibilities[item.name]} disabled={!responsibilities[item.name]}/> </View> </View>))} </View> <View style={styles.modalFooter}> <Pressable onPress={handleSave} style={[styles.btnApplySettings, { backgroundColor: theme.buttonBackgroundColor }]}> <Text style={[styles.btnText, { color: theme.buttonTextColor }]}>Save</Text> </Pressable> </View> </ScrollView> </>); const content = renderContent ? renderContent({ defaultContent, dimensions }) : defaultContent; const defaultContainer = (<Modal transparent animationType="slide" visible={isCoHostModalVisible} onRequestClose={onCoHostClose}> <View style={[styles.modalContainer, getModalPosition({ position })]}> <View style={[ styles.modalContent, { width: modalWidth, backgroundColor: backgroundColor }, shouldUseModernTheme ? { borderColor: theme.borderColor, borderWidth: 1 } : null, style, ]}> {content} </View> </View> </Modal>); return renderContainer ? renderContainer({ defaultContainer, dimensions }) : defaultContainer; }; export default CoHostModal; const styles = StyleSheet.create({ modalContainer: { flex: 1, justifyContent: 'flex-end', alignItems: 'flex-end', zIndex: 9, elevation: 9, }, modalContent: { height: '65%', backgroundColor: '#83c0e9', borderRadius: 0, padding: 20, maxHeight: '65%', maxWidth: '70%', zIndex: 9, elevation: 9, }, modalHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 15, }, modalTitle: { fontSize: 18, fontWeight: 'bold', color: 'black', }, text: { color: 'black', }, btnCloseSettings: { padding: 5, }, modalBody: { flex: 1, }, formCheckGroup: { marginBottom: 10, }, formCheck: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10, }, sep: { height: 1, backgroundColor: '#ffffff', marginVertical: 2, }, hr: { height: 1, backgroundColor: 'black', marginVertical: 5, }, input: { fontSize: 14, paddingVertical: 4, paddingHorizontal: 8, borderWidth: 1, borderColor: 'gray', borderRadius: 4, color: 'black', paddingRight: 20, backgroundColor: 'white', }, disabledInput: { backgroundColor: '#f2f2f2', }, formGroup: { marginBottom: 10, }, label: { fontSize: 14, color: 'black', marginBottom: 5, }, modalFooter: { marginTop: 10, flexDirection: 'row', justifyContent: 'flex-end', }, btnApplySettings: { flex: 1, padding: 5, borderRadius: 5, justifyContent: 'center', alignItems: 'center', backgroundColor: 'black', }, btnText: { color: 'white', fontSize: 14, }, icon: { fontSize: 24, color: 'black', }, row: { flexDirection: 'row', marginBottom: 10, }, col5: { flex: 5, }, col3: { flex: 3, alignItems: 'center', }, col4: { flex: 4, alignItems: 'center', }, }); const pickerSelectStyles = StyleSheet.create({ inputIOS: { fontSize: 14, paddingVertical: 12, paddingHorizontal: 10, borderWidth: 1, borderColor: 'gray', borderRadius: 4, color: 'black', paddingRight: 30, backgroundColor: 'white', }, inputAndroid: { fontSize: 14, paddingHorizontal: 10, paddingVertical: 8, borderWidth: 0.5, borderColor: 'purple', borderRadius: 8, color: 'black', paddingRight: 30, backgroundColor: 'white', }, inputWeb: { fontSize: 14, paddingHorizontal: 5, paddingVertical: 1, borderWidth: 0.5, borderColor: 'purple', borderRadius: 8, color: 'black', paddingRight: 30, // To ensure the text is never behind the icon backgroundColor: 'white', marginBottom: 10, }, }); //# sourceMappingURL=CoHostModal.js.map