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

302 lines (299 loc) 12.3 kB
import React, { useEffect, useState } from 'react'; import { Modal, Platform, Pressable, ScrollView, StyleSheet, Text, View, useWindowDimensions, } from 'react-native'; import { FontAwesome5 } from '@expo/vector-icons'; import RNPickerSelect from 'react-native-picker-select'; import { modifyDisplaySettings } from '../../methods/displaySettingsMethods/modifyDisplaySettings'; import { getModalPosition } from '../../methods/utils/getModalPosition'; import { getModernColors, getModernModalCardStyle, getModernSidePanelStyle, resolveIsDarkMode, } from '../core/modernTheme'; import { createThemedPickerSelectStyles, getModalBodyTheme } from '../core/modalBodyTheme'; export const ModernDisplaySettingsModal = ({ isDisplaySettingsModalVisible, onDisplaySettingsClose, onModifyDisplaySettings = modifyDisplaySettings, parameters, position = 'topRight', backgroundColor, isDarkMode, style, renderContent, renderContainer, renderMode = 'modal', }) => { const { width: windowWidth, height: windowHeight } = useWindowDimensions(); const darkMode = resolveIsDarkMode({ isDarkMode, backgroundColor, parameters }); const colors = getModernColors(darkMode); const modalTheme = getModalBodyTheme(darkMode); const pickerTheme = createThemedPickerSelectStyles(modalTheme); const isEmbedded = renderMode === 'sidebar' || renderMode === 'inline'; const useDesktopSidePanel = !isEmbedded && Platform.OS === 'web' && windowWidth >= 1200; const modalWidth = Math.min(Math.max(windowWidth * 0.84, 320), useDesktopSidePanel ? 420 : 460); const modalHeight = Math.min(Math.max(windowHeight * 0.72, 460), 760); const dimensions = { width: modalWidth, height: modalHeight }; const [meetingDisplayTypeState, setMeetingDisplayTypeState] = useState(parameters.meetingDisplayType); const [autoWaveState, setAutoWaveState] = useState(parameters.autoWave); const [forceFullDisplayState, setForceFullDisplayState] = useState(parameters.forceFullDisplay); const [showSubtitlesOnCardsState, setShowSubtitlesOnCardsState] = useState(parameters.showSubtitlesOnCards ?? true); const [meetingVideoOptimizedState, setMeetingVideoOptimizedState] = useState(parameters.meetingVideoOptimized); useEffect(() => { if (!isDisplaySettingsModalVisible) { return; } setMeetingDisplayTypeState(parameters.meetingDisplayType); setAutoWaveState(parameters.autoWave); setForceFullDisplayState(parameters.forceFullDisplay); setShowSubtitlesOnCardsState(parameters.showSubtitlesOnCards ?? true); setMeetingVideoOptimizedState(parameters.meetingVideoOptimized); }, [ isDisplaySettingsModalVisible, parameters.autoWave, parameters.forceFullDisplay, parameters.meetingDisplayType, parameters.meetingVideoOptimized, parameters.showSubtitlesOnCards, ]); if (!isEmbedded && !isDisplaySettingsModalVisible) { return null; } const handleSaveSettings = async () => { await onModifyDisplaySettings({ parameters: { ...parameters, meetingDisplayType: meetingDisplayTypeState, autoWave: autoWaveState, forceFullDisplay: forceFullDisplayState, showSubtitlesOnCards: showSubtitlesOnCardsState, meetingVideoOptimized: meetingVideoOptimizedState, }, }); onDisplaySettingsClose(); }; const renderToggleRow = (title, description, value, onToggle) => (<View style={[styles.toggleRow, { borderColor: colors.border, backgroundColor: colors.surfaceStrong }]}> <View style={styles.toggleCopy}> <Text style={[styles.toggleTitle, { color: colors.text }]}>{title}</Text> <Text style={[styles.toggleDescription, { color: colors.textMuted }]}>{description}</Text> </View> <Pressable accessibilityRole="switch" accessibilityLabel={title} onPress={onToggle} style={[ styles.toggleButton, { borderColor: value ? colors.accent : colors.border, backgroundColor: value ? colors.accentSoft : colors.surfaceMuted, }, ]}> <FontAwesome5 name={value ? 'check' : 'minus'} size={13} color={value ? colors.accent : colors.textMuted}/> </Pressable> </View>); const contentBody = (<View style={styles.contentShell}> <View style={[styles.header, { borderBottomColor: colors.border }]}> <View style={styles.titleRow}> <View style={[styles.titleIcon, { backgroundColor: colors.accentSoft }]}> <FontAwesome5 name="desktop" size={15} color={colors.accent}/> </View> <View style={styles.titleCopy}> <Text style={[styles.titleText, { color: colors.text }]}>Display Settings</Text> <Text style={[styles.subtitleText, { color: colors.textMuted }]}>Adjust layout behavior, animation, and viewing preferences</Text> </View> </View> <Pressable accessibilityRole="button" accessibilityLabel="Close display settings" onPress={onDisplaySettingsClose} style={({ pressed }) => [ styles.iconButton, { borderColor: colors.border, backgroundColor: pressed ? colors.accentSoft : colors.surfaceStrong, }, ]}> <FontAwesome5 name="times" size={14} color={colors.textMuted}/> </Pressable> </View> <ScrollView contentContainerStyle={styles.bodyContent} showsVerticalScrollIndicator={false} style={styles.body}> <View style={[styles.panel, { backgroundColor: colors.surfaceStrong, borderColor: colors.border }]}> <Text style={[styles.panelEyebrow, { color: colors.textMuted }]}>Layout</Text> <Text style={[styles.panelTitle, { color: colors.text }]}>Participant display mode</Text> <RNPickerSelect onValueChange={(value) => setMeetingDisplayTypeState(value)} items={[ { label: 'Video Participants Only', value: 'video' }, { label: 'Media Participants Only', value: 'media' }, { label: 'Show All Participants', value: 'all' }, ]} value={meetingDisplayTypeState} style={pickerTheme} placeholder={{}} useNativeAndroidPickerStyle={false}/> </View> <View style={[styles.panel, { backgroundColor: colors.surfaceStrong, borderColor: colors.border }]}> <Text style={[styles.panelEyebrow, { color: colors.textMuted }]}>Playback</Text> <Text style={[styles.panelTitle, { color: colors.text }]}>Display behavior</Text> <View style={styles.toggleStack}> {renderToggleRow('Display Audiographs', 'Animate participant cards based on current audio levels.', autoWaveState, () => setAutoWaveState((value) => !value))} {renderToggleRow('Force Full Display', 'Keep the active layout expanded instead of adapting to the room.', forceFullDisplayState, () => setForceFullDisplayState((value) => !value))} {renderToggleRow('Force Video Participants', 'Bias the layout toward video participants when media is mixed.', meetingVideoOptimizedState, () => setMeetingVideoOptimizedState((value) => !value))} {renderToggleRow('Show Subtitles on Cards', 'Keep subtitle text visible directly on participant cards.', showSubtitlesOnCardsState, () => setShowSubtitlesOnCardsState((value) => !value))} </View> </View> </ScrollView> <View style={[styles.footer, { borderTopColor: colors.border }]}> <Pressable accessibilityRole="button" accessibilityLabel="Save display settings" onPress={() => void handleSaveSettings()} style={({ pressed }) => [ styles.primaryButton, { backgroundColor: pressed ? colors.accentAlt : colors.accent, borderColor: pressed ? colors.accentAlt : colors.accent, }, ]}> <Text style={[styles.primaryButtonText, { color: colors.invertedText }]}>Save</Text> </Pressable> </View> </View>); const content = renderContent ? renderContent({ defaultContent: contentBody, dimensions }) : contentBody; if (isEmbedded) { return (<View style={styles.embeddedContainer}> <View style={[styles.embeddedShell, { backgroundColor: backgroundColor ?? colors.surface }, style]}>{content}</View> </View>); } const overlayStyle = [ styles.modalContainer, useDesktopSidePanel ? styles.desktopSidebarContainer : getModalPosition({ position }), { backgroundColor: useDesktopSidePanel ? 'rgba(15, 23, 42, 0.14)' : 'rgba(15, 23, 42, 0.28)' }, ]; const shellStyle = [ styles.modalShell, getModernModalCardStyle(darkMode), useDesktopSidePanel ? getModernSidePanelStyle(darkMode) : { width: modalWidth, height: modalHeight }, { backgroundColor: backgroundColor ?? colors.surface }, style, ]; const defaultContainer = (<Modal animationType="fade" transparent visible={isDisplaySettingsModalVisible} onRequestClose={onDisplaySettingsClose}> <Pressable onPress={onDisplaySettingsClose} style={overlayStyle}> <Pressable onPress={() => undefined} style={shellStyle}> {content} </Pressable> </Pressable> </Modal>); return renderContainer ? renderContainer({ defaultContainer, dimensions }) : defaultContainer; }; const styles = StyleSheet.create({ modalContainer: { flex: 1, padding: 12, }, desktopSidebarContainer: { justifyContent: 'flex-start', alignItems: 'flex-end', padding: 0, }, modalShell: { overflow: 'hidden', }, embeddedContainer: { flex: 1, minHeight: 0, width: '100%', }, embeddedShell: { flex: 1, minHeight: 0, width: '100%', }, contentShell: { flex: 1, minHeight: 0, }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 18, paddingVertical: 16, borderBottomWidth: 1, }, titleRow: { flexDirection: 'row', alignItems: 'center', gap: 12, flexShrink: 1, }, titleCopy: { flexShrink: 1, }, titleIcon: { width: 34, height: 34, borderRadius: 17, alignItems: 'center', justifyContent: 'center', }, titleText: { fontSize: 18, fontWeight: '800', }, subtitleText: { fontSize: 12, lineHeight: 18, marginTop: 2, }, iconButton: { width: 34, height: 34, borderRadius: 17, borderWidth: 1, alignItems: 'center', justifyContent: 'center', }, body: { flex: 1, }, bodyContent: { padding: 18, gap: 14, }, panel: { borderWidth: 1, borderRadius: 18, padding: 16, }, panelEyebrow: { fontSize: 11, fontWeight: '700', letterSpacing: 0.6, textTransform: 'uppercase', marginBottom: 4, }, panelTitle: { fontSize: 16, fontWeight: '800', marginBottom: 12, }, toggleStack: { gap: 10, }, toggleRow: { flexDirection: 'row', alignItems: 'center', gap: 12, borderWidth: 1, borderRadius: 14, paddingHorizontal: 14, paddingVertical: 12, }, toggleCopy: { flex: 1, }, toggleTitle: { fontSize: 14, fontWeight: '700', }, toggleDescription: { fontSize: 12, lineHeight: 18, marginTop: 2, }, toggleButton: { width: 34, height: 34, borderRadius: 17, borderWidth: 1, alignItems: 'center', justifyContent: 'center', }, footer: { borderTopWidth: 1, paddingHorizontal: 18, paddingTop: 14, paddingBottom: 18, }, primaryButton: { minHeight: 46, borderRadius: 14, borderWidth: 1, alignItems: 'center', justifyContent: 'center', }, primaryButtonText: { fontSize: 14, fontWeight: '800', }, }); export default ModernDisplaySettingsModal; //# sourceMappingURL=ModernDisplaySettingsModal.js.map