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
157 lines (153 loc) • 10.7 kB
JavaScript
import React, { useState, useMemo, useEffect, useCallback } from "react";
import { View, Text, TouchableOpacity, TextInput, StyleSheet, Modal, ScrollView, Dimensions } from "react-native";
import { FontAwesome5 } from "@expo/vector-icons";
import { updateParticipantPermission } from "../../methods/permissionsMethods/updateParticipantPermission";
import { getModalPosition } from "../../methods/utils/getModalPosition";
import { getModalBodyTheme } from "../../components_modern/core/modalBodyTheme";
const { width, height } = Dimensions.get("window");
const PermissionsModal = ({ isPermissionsModalVisible, onPermissionsClose, parameters, backgroundColor = "#1e293b", isDarkMode, position = "center", style, renderMode = "modal", renderContainer, }) => {
const theme = getModalBodyTheme(isDarkMode);
const { participants: initialParticipants, member, islevel, socket, roomName, showAlert, coHostResponsibility: initialCoHostResponsibility = [], updateCoHostResponsibility, getUpdatedAllParams, } = parameters;
const [searchFilter, setSearchFilter] = useState("");
const [participantsState, setParticipantsState] = useState(initialParticipants || []);
const [coHostResponsibility, setCoHostResponsibility] = useState(initialCoHostResponsibility);
useEffect(() => {
if (isPermissionsModalVisible && getUpdatedAllParams) {
const freshParams = getUpdatedAllParams();
setParticipantsState(freshParams.participants || []);
if (freshParams.coHostResponsibility) {
setCoHostResponsibility(freshParams.coHostResponsibility);
}
}
}, [getUpdatedAllParams, isPermissionsModalVisible]);
const participants = participantsState;
const availableParticipants = useMemo(() => {
return participants
.filter((p) => p.islevel !== "2" && p.name !== member) // Not host and not self
.filter((p) => (searchFilter ? p.name.toLowerCase().includes(searchFilter.toLowerCase()) : true));
}, [participants, member, searchFilter]);
const isHost = islevel === "2";
const toggleCoHostResponsibility = (item) => {
const updated = coHostResponsibility.map((r) => r.name === item.name ? { ...r, value: !r.value } : r);
setCoHostResponsibility(updated);
updateCoHostResponsibility?.(updated);
// would also call a method here to emit to server if defined
};
const handleTogglePermission = useCallback(async (participant, action) => {
const nextLevel = participant.islevel === "1" ? "0" : "1";
if (action === "audio" || action === "video" || action === "screenshare" || action === "chat") {
await updateParticipantPermission({
socket,
participant,
newLevel: nextLevel,
member,
islevel,
roomName,
showAlert,
});
}
}, [socket, roomName, member, islevel, showAlert]);
if (!isPermissionsModalVisible)
return null;
const isEmbedded = renderMode === "sidebar" || renderMode === "inline";
const content = (<>
<View style={isEmbedded ? styles.embeddedHeader : styles.header}>
<View style={styles.headerLeft}>
<View style={[styles.headerIcon, isEmbedded && styles.embeddedHeaderIcon]}>
<FontAwesome5 name="user-shield" size={16} color={isEmbedded ? theme.accentColor : "white"}/>
</View>
<View>
<Text style={[isEmbedded ? styles.embeddedTitle : styles.headerTitle, { color: isEmbedded ? theme.textColor : "white" }]}>Permissions</Text>
{isEmbedded && <Text style={[styles.embeddedSubtitle, { color: theme.mutedTextColor }]}>Participant access and roles</Text>}
</View>
</View>
{!isEmbedded && (<TouchableOpacity onPress={onPermissionsClose} style={styles.closeButton}>
<FontAwesome5 name="times" size={16} color="white"/>
</TouchableOpacity>)}
</View>
<ScrollView style={styles.content} contentContainerStyle={styles.contentInner}>
{/* Responsibilities Section */}
{isHost && coHostResponsibility.length > 0 && (<View style={styles.section}>
<Text style={[styles.sectionTitle, { color: theme.textColor }]}>Co-Host Roles</Text>
<View style={styles.rolesList}>
{coHostResponsibility.map((item, index) => (<TouchableOpacity key={index} onPress={() => toggleCoHostResponsibility(item)} style={styles.roleItem}>
<View style={[styles.checkbox, item.value && styles.checkboxChecked]}>
{item.value && <FontAwesome5 name="check" size={10} color="white"/>}
</View>
<Text style={[styles.roleText, { color: theme.textColor }]}>{item.name}</Text>
</TouchableOpacity>))}
</View>
</View>)}
{/* Participants Permissions Section */}
{isHost && (<View style={styles.section}>
<Text style={[styles.sectionTitle, { color: theme.textColor }]}>Participant Level Access</Text>
<View style={[styles.searchContainer, { backgroundColor: theme.inputBackgroundColor, borderColor: theme.borderColor, borderWidth: 1 }]}>
<FontAwesome5 name="search" size={14} color={theme.mutedTextColor} style={styles.searchIcon}/>
<TextInput style={[styles.searchInput, { color: theme.inputTextColor }]} placeholder="Search participants..." placeholderTextColor={theme.placeholderTextColor} value={searchFilter} onChangeText={setSearchFilter}/>
{searchFilter.length > 0 && (<TouchableOpacity onPress={() => setSearchFilter("")}>
<FontAwesome5 name="times-circle" size={14} color={theme.mutedTextColor}/>
</TouchableOpacity>)}
</View>
{availableParticipants.length === 0 ? (<Text style={[styles.emptyText, { color: theme.mutedTextColor }]}>No participants found</Text>) : (availableParticipants.map((p) => (<View key={p.id} style={[styles.participantItem, { backgroundColor: theme.rowBackgroundColor }]}>
<Text style={[styles.participantName, { color: theme.textColor }]}>{p.name}</Text>
<View style={styles.actionRow}>
<TouchableOpacity style={styles.actionBtn} onPress={() => handleTogglePermission(p, "audio")}>
<FontAwesome5 name="microphone" size={14} color="#a855f7"/>
</TouchableOpacity>
<TouchableOpacity style={styles.actionBtn} onPress={() => handleTogglePermission(p, "video")}>
<FontAwesome5 name="video" size={14} color="#a855f7"/>
</TouchableOpacity>
</View>
</View>)))}
</View>)}
</ScrollView>
</>);
if (isEmbedded) {
return (<View style={[styles.embeddedContainer, { backgroundColor, borderColor: theme.borderColor }, style]}>
{content}
</View>);
}
const defaultContainer = (<Modal visible={isPermissionsModalVisible} transparent={true} animationType="fade" onRequestClose={onPermissionsClose}>
<TouchableOpacity style={[styles.overlay, getModalPosition({ position })]} activeOpacity={1} onPress={onPermissionsClose}>
<TouchableOpacity style={[styles.modalContainer, { backgroundColor, borderColor: theme.borderColor }, style]} activeOpacity={1}>
{content}
</TouchableOpacity>
</TouchableOpacity>
</Modal>);
return renderContainer
? renderContainer({ defaultContainer, dimensions: { width, height } })
: defaultContainer;
};
const styles = StyleSheet.create({
overlay: { flex: 1, backgroundColor: "rgba(0,0,0,0.55)", justifyContent: "center", alignItems: "center" },
modalContainer: { width: Math.min(450, width * 0.9), maxHeight: height * 0.85, borderRadius: 16, overflow: "hidden", borderWidth: 1, borderColor: "rgba(255,255,255,0.1)" },
embeddedContainer: { flex: 1, width: "100%", height: "100%", borderRadius: 0, borderWidth: 0, overflow: "hidden" },
header: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", padding: 16, backgroundColor: "#8b5cf6" },
embeddedHeader: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", paddingHorizontal: 16, paddingVertical: 14, borderBottomWidth: 1, borderBottomColor: "rgba(148,163,184,0.22)" },
headerLeft: { flexDirection: "row", alignItems: "center", gap: 10 },
headerIcon: { alignItems: "center", justifyContent: "center" },
embeddedHeaderIcon: { width: 34, height: 34, borderRadius: 17, backgroundColor: "rgba(56,189,248,0.12)", marginRight: 10 },
headerTitle: { color: "white", fontSize: 18, fontWeight: "600", marginLeft: 10 },
embeddedTitle: { fontSize: 18, fontWeight: "700" },
embeddedSubtitle: { fontSize: 12, marginTop: 2 },
closeButton: { backgroundColor: "rgba(255,255,255,0.2)", padding: 8, borderRadius: 8 },
content: { flex: 1 },
contentInner: { padding: 16, paddingBottom: 22 },
section: { marginBottom: 20 },
sectionTitle: { color: "white", fontSize: 16, fontWeight: "600", marginBottom: 10 },
rolesList: { flexDirection: "row", flexWrap: "wrap", gap: 10 },
roleItem: { flexDirection: "row", alignItems: "center", width: "45%", marginBottom: 10 },
checkbox: { width: 18, height: 18, borderRadius: 4, borderWidth: 1, borderColor: "white", justifyContent: "center", alignItems: "center", marginRight: 8 },
checkboxChecked: { backgroundColor: "#8b5cf6", borderColor: "#8b5cf6" },
roleText: { color: "white", fontSize: 14 },
searchContainer: { flexDirection: "row", alignItems: "center", backgroundColor: "rgba(0,0,0,0.3)", borderRadius: 8, paddingHorizontal: 12, height: 40, marginBottom: 12 },
searchIcon: { marginRight: 8 },
searchInput: { flex: 1, color: "white" },
emptyText: { color: "rgba(255,255,255,0.5)", textAlign: "center", marginTop: 20 },
participantItem: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", backgroundColor: "rgba(255,255,255,0.05)", padding: 12, borderRadius: 8, marginBottom: 8 },
participantName: { color: "white", fontSize: 15 },
actionRow: { flexDirection: "row", gap: 12 },
actionBtn: { backgroundColor: "rgba(139,92,246,0.15)", padding: 8, borderRadius: 6 }
});
export default PermissionsModal;
//# sourceMappingURL=PermissionsModal.js.map