UNPKG

sfm-uikit-react-native

Version:

It is a react native component for SmartFloMeet users.

1,511 lines (1,375 loc) 149 kB
import React, { PureComponent} from "react"; import { EnxSetting ,EnxPageSlideEventName} from ".."; import { View, Animated, Button, Dimensions, Easing, TouchableOpacity, Text, TouchableWithoutFeedback, TouchableHighlight, ToastAndroid, AlertIOS, FlatList, Image, Switch, Alert, Modal,ScrollView } from "react-native"; import EnxParticipantScreen from "./EnxParticipantScreen"; import EnxChatScreen from "./EnxChatScreen"; import { styles } from "../style/EnxVideoViewStyle"; import "./ignoreWarnings" import Blink from './Blink' import Dialog from "react-native-dialog"; import EnxConfirmationScreen from "./EnxConfirmationScreen" import EnxBottomView from "./EnxBottomView"; import EnxMoreScreen from "./EnxMoreScreen"; import EnxRoomSetting from "./EnxRoomSetting"; import EnxLobbyView from "./EnxLobbyView"; import EnxQnaScreen from "./EnxQnaScreen"; import EnxPollingScreen from "./EnxPollingScreen"; import EnxCreatePollScreen from "./EnxCreatePollScreen" import PollAnswerDialog from './PollAnswerDialog'; import PieChart from '../commonClass/EnxPieChart' import EnxPollResultDialog from './EnxPollResultDialog' import AnswerDialog from "./AnswerDialog"; import { pick } from "underscore"; import { EnxRoom, Enx, EnxStream, EnxPlayerView, EnxToolBarView } from "sfm-rtc-react-native"; import SelectDropdown from 'react-native-select-dropdown'; import { EnxPubMode, EnxPubType } from "sfm-rtc-react-native/src/Enx"; import { Modalize } from 'react-native-modalize'; const calculateRow = (data)=>{ if(data.length == 1) return 1; else if(data.length == 2 || data.length == 3 || data.length == 4) return 2 else if(data.length == 5 || data.length == 6) return 3 else if(data.length == 7 || data.length == 8) return 4 else if(data.length == 9 || data.length == 10 || data.length>10) return 5 } const calculateColumn=data=>{ if(data.length == 1||data.length == 2||data.lenght===3 ) return 1; else if(data.length >3 ) return 2 } export default class EnxVideoView extends PureComponent { calculatePlayerHeight = () => { const numColumns = calculateColumn(this.state.activeTalkerStreams.length); const isLandscape = this.state.windowWidth > this.state.windowHeight; if (isLandscape) { return this.state.windowHeight / numColumns; // Fixed height per player in landscape } else { const numRows = calculateRow(this.state.activeTalkerStreams); return this.state.windowHeight /numRows // Max 2 rows in portrait } }; renderItem = ({ item, index }) => { try{ return ( <TouchableWithoutFeedback onPress={ () => {this.setState({isToolBarsVisible:!this.state.isToolBarsVisible })}} onLongPress={ () => {this.startAnnotation(index)}} > <EnxPlayerView style={{ flex: 1, margin: 1, backgroundColor:'black', height:this.state.mActiveStreamId==null? (this.state.windowHeight ) / calculateRow(this.state.activeTalkerStreams):this.state.windowWidth > this.state.windowHeight?(this.state.windowHeight ) / calculateRow(this.state.activeTalkerStreams):(this.state.screenWindowHeight ) / calculateRow(this.state.activeTalkerStreams), }} key={String(item.streamId)} streamId={String(item.streamId)} isLocal = "remote" /> </TouchableWithoutFeedback> ); }catch(err){ console.log(err.message) } }; renderItem1 = ({ item, index }) => { try{ return ( <TouchableWithoutFeedback onPress={ () => {this.setState({isToolBarsVisible:!this.state.isToolBarsVisible })}} onLongPress={ () => {this.startAnnotation(index)}} > <EnxPlayerView style={{ flex: 1, margin: 1, backgroundColor:'black', height:this.state.activeTalkerStreams.length<4?this.state.mActiveStreamId==null?this.state.windowHeight :this.state.screenWindowHeight:this.mActiveStreamId==null? (this.state.windowHeight ) / 2:this.state.windowWidth > this.state.windowHeight?(this.state.windowHeight ) / 2:(this.state.screenWindowHeight ) / 2, }} key={String(item.streamId)} streamId={String(item.streamId)} isLocal = "remote" /> </TouchableWithoutFeedback> ); }catch(err){ console.log(err.message) } }; createLandScapeView(){ console.log("=======design landscape view =============") try{ return( <HorizontalList data={this.state.activeTalkerStreams}> </HorizontalList> ); }catch(err){ console.log(err.message) } } constructor(props) { super(props); // Create a ref to access EnxBottomView's methods this.bottomViewRef = React.createRef(); //Create a ref to access EnxParticipant Screen this.partRef = React.createRef(); //Create a ref to access EnxMore Screen this.moreRef = React.createRef(); this.pollRef = React.createRef(); this.qnaRef = React.createRef(); this.pollAnswerRef=React.createRef(); const screenHeight = Dimensions.get("window").height; this.state = { currentOverlay: null, // Screens that will be overlaid the pratent screen animationType: "horizontal", // Track the animation direction slideAnim: new Animated.Value(Dimensions.get("window").width), // Slide from left to right or vice versa slideAnimHeight : new Animated.Value(Dimensions.get("window").height), //// Slide animation from bottom upaniminationSize : 0, // Set position of transcation from up to down slideAnimationSize : Dimensions.get("window").width, //this will set X cordinate for animation bottomScreenHeight: new Animated.Value(85), // Initial height of EnxButtomScreen isBottomScreenExpanded: false, // Track whether the bottom screen is expanded windowHeight: screenHeight, // Store current screen height - This is for orientation windowWidth: Dimensions.get("window").width, // Store current screen width- This is for orientation animationDuration : 300, screenWindowHeight: Dimensions.get("window").height*0.3, role :'moderator', localStreamId:'', confNumber:'', selfUserName:'', senderId:'', selfUserRef:'', clientID : '123-123', isConnected:false, activeTalkerStreams: [], awaitedParticipantList:[], audioMuteUnmuteCheck:true, videoMuteUnmuteCheck: true, rotateCamera: false, participantList:[], chatModelList:[],// store chat Model groupChatModel:[],// store group chat Model privateChatModel:[],// store private chat Model selfClientId:"", participantClientId:"", chatType:'', muteRoomCheck: false, recordingCheck: false, recordingImage: require("../image_asset/recording_on.png"), isRoomAwaited:false, awaitedParticipantList:[], isLobbyDialog:false, isRoomSetting:false, isModerator:false, shareModeSelected:"", screenShareMode:"", screenShareIndex:0, screenShareState:"", shareClientId:"", requestClientId:"", screenShareModeGranted : false, isShareRequested : false, shareRequestList:[], floorRequestList:[], participantFloorAction: 0, // Equivalent to private var participantFloorAction = 0 currentClientPosition: -1, isRequest:false, mActiveStreamId:null, heightOfShareView:'0%', widthOfShareView:'0%', selectedDevice: "Earpiece", deviceList: [], isSwitchMedia:false, isChatViewVisible: false, isMoreVisible:false, screenShareId:null, screenShareCheck:false, isToolBarsVisible:true, isShowAnswerDialog:false, isShowResultDialog:false, isShowTypeAnswerDialog:false, typeQnaObject:{}, pollResultData:{}, question:'', pollData:{}, answers:[], duration:0, pollsList: [], qnaList:[], roomInfo: { allow_reconnect: true, number_of_attempts: 3, timeout_interval: 15, playerConfiguration: { audiomute: true, videomute: true, bandwidth: true, screenshot: true, avatar: true, iconHeight: 30, iconWidth: 30, avatarHeight: 50, avatarWidth: 50, iconColor : "#0000FF", }, } }; // Add orientation change listener this.addOrientationObserver(); } // Remove orientation listener componentWillUnmount() { this.removeOrientationObserver(); } addOrientationObserver =() =>{ // Add orientation change listener this.orientationChangeListener = Dimensions.addEventListener("change", this.onOrientationChange); } //Handle notification lisner from EnxSetting class handleVideoEvent = (data) => { console.log('Received event:', data); }; //removeOrientation Decetation observer removeOrientationObserver = () =>{ if (this.orientationChangeListener && this.orientationChangeListener.remove) { this.orientationChangeListener.remove(); } EnxSetting.removeEventListener('videoEvent', this.handleVideoEvent); } // Handle orientation changes to adapt the floating view's height onOrientationChange = ({ window: { height, width } }) => { const { isBottomScreenExpanded } = this.state; // Update height and width based on new orientation this.setState({ windowHeight: height, windowWidth: width, slideAnimationSize : this.state.currentOverlay == 'more' ? Dimensions.get("window").width + 10 : - Dimensions.get("window").width, slideAnim: new Animated.Value(width), // Reset slide animation based on new width slideAnimHeight: new Animated.Value(height), // Reset slide animation height animationDuration : 0, }); // If expanded, adjust bottom screen height based on the new dimensions const targetHeight = isBottomScreenExpanded ? height * 0.75 : 85; this.setState({ bottomScreenHeight: new Animated.Value(targetHeight)}); if(this.state.currentOverlay != null){ setTimeout(() => { if(this.state.animationType == 'horizontal'){ this.navigateToIn(this.state.currentOverlay) } else{ this.navigateToUp(this.state.currentOverlay) } }, 50); } }; //Check flag for confirmation screen componentDidMount() { // Check EnxSetting to see if we should show the confirmation screen by default if (EnxSetting.getIsShowConfirmationScreen()){ // If the confirmation screen needs to be shown by default, navigate to it this.setState({ currentOverlay: "confirmation" , animationType: "horizontal",slideAnimationSize : (this.state.currentOverlay == 'more' || this.state.currentOverlay == 'Room-Setting'|| this.state.currentOverlay == 'QNA-Page'|| this.state.currentOverlay == 'Polling-Page') ? Dimensions.get("window").width + 10 : - Dimensions.get("window").width}, this.runSlideInAnimation); } //Add listener for notification EnxSetting.addEventListener('videoEvent', this.handleVideoEvent); } //changes animationDuration if click to load page, not load page with orientation changeDuration =() =>{ this.setState({ animationDuration : 300 }) } //Set Click Action MEthod for slide Animination //Here we are changing the duration first and then animating setActionMethodForInAnimation =(screenName) => { this.changeDuration() // Delay the second method by 2 seconds (2000 milliseconds) setTimeout(() => { this.navigateToIn(screenName) }, 100); // Delay in milliseconds } //Set Click Action MEthod for slideOut Animination //Here we are changing the duration first and then animating setActionMethodForOutAnimation =() => { this.changeDuration() this.setState({isMoreVisible:false}) // Delay the second method by 2 seconds (2000 milliseconds) setTimeout(() => { this.runSlideOutAnimation() }, 100); // Delay in milliseconds } //Set Click Action MEthod for Up Animination //Here we are changing the duration first and then animating setActionMethodForUpAnimation = (screenName) =>{ this.changeDuration() // Delay the second method by 2 seconds (2000 milliseconds) setTimeout(() => { this.navigateToUp(screenName) }, 100); // Delay in milliseconds } //Here we are changing the duration first and then animating setActionMethodForDownAnimation = () =>{ this.changeDuration() // Delay the second method by 2 seconds (2000 milliseconds) setTimeout(() => { this.runSlideDownAnimation() }, 100); // Delay in milliseconds } // Navigate and trigger the slide-in/out animation navigateToIn = (screenName) => { this.setState({ currentOverlay: screenName , animationType: "horizontal" , slideAnimationSize : (screenName == 'more' || screenName == 'Room-Setting' || screenName == 'QNA-Page'||screenName=='Polling-Page'||screenName=='createPoll')? Dimensions.get("window").width + 10 : - Dimensions.get("window").width}, this.runSlideInAnimation); } // Navigate and trigger the slide-up/Down animation navigateToUp = (screenName) => { this.setState({ currentOverlay: screenName , animationType: "vertical", upaniminationSize : 0}, this.runSlideUpAnimation); } // Slide-in animation for overlay runSlideInAnimation = () => { this.state.slideAnim.setValue(this.state.slideAnimationSize); // Start off-screen Animated.timing(this.state.slideAnim, { toValue: 0, // Slide to center duration: this.state.animationDuration, useNativeDriver: true, easing: Easing.ease, }).start(() => { //this.removeOrientationObserver() if(this.state.currentOverlay == 'chat'){ this.props.onPageSlide(EnxPageSlideEventName.EnxChat,true); } else if(this.state.currentOverlay == 'QNA-Page'){ this.props.onPageSlide(EnxPageSlideEventName.EnxQnA,true); }else if(this.state.currentOverlay == 'Polling-Page'){ this.props.onPageSlide(EnxPageSlideEventName.EnxPolling,true); }else if(this.state.currentOverlay == 'createPoll'){ this.props.onPageSlide(EnxPageSlideEventName.EnxCreatePoll,true); } }); }; // Slide-out animation when closing the overlay runSlideOutAnimation = (callback) => { Animated.timing(this.state.slideAnim, { toValue: this.state.slideAnimationSize, // Slide off-screen duration: this.state.animationDuration, useNativeDriver: true, easing: Easing.ease, }).start(() => { //this.addOrientationObserver(); if(this.state.currentOverlay == 'chat'){ this.props.onPageSlide(EnxPageSlideEventName.EnxChat,false); }else if(this.state.currentOverlay == 'QNA-Page'){ this.props.onPageSlide(EnxPageSlideEventName.EnxQnA,false); }else if(this.state.currentOverlay == 'Polling-Page'){ this.props.onPageSlide(EnxPageSlideEventName.EnxPolling,false); }else if(this.state.currentOverlay == 'createPoll'){ this.props.onPageSlide(EnxPageSlideEventName.EnxCreatePoll,false); } this.setState({ currentOverlay: null }); // Remove overlay after animation if (callback) callback(); }); }; // Slide-up animation for overlay runSlideUpAnimation = () => { this.state.slideAnimHeight.setValue(Dimensions.get("window").height); // Start up-screen Animated.timing(this.state.slideAnimHeight, { toValue: this.state.upaniminationSize, // Slide to up duration: this.state.animationDuration, useNativeDriver: true, easing: Easing.ease, }).start(() => { //this.removeOrientationObserver() }); }; // Slide-Down animation when closing the overlay runSlideDownAnimation = (callback) => { Animated.timing(this.state.slideAnimHeight, { toValue: Dimensions.get("window").height, // Slide Down-screen duration: this.state.animationDuration, useNativeDriver: true, easing: Easing.ease, }).start(() => { //this.addOrientationObserver(); this.setState({ currentOverlay: null }); // Remove overlay after animation if (callback) callback(); }); }; //Load confrence screen renderConfirmationScreen = () => ( <EnxConfirmationScreen onConfirm={() => this.setActionMethodForOutAnimation()} /> ); //Load Chat screen renderChatScreen = () => ( // <EnxChatScreen onBack={() => this.runSlideDownAnimation()} /> <EnxChatScreen onBack={() => this.setActionMethodForOutAnimation()} data = {this.state.chatType === 'group'?this.state.groupChatModel:this.state.privateChatModel} selfClientId = {this.state.selfClientId} participantClientId = {this.state.participantClientId} chatType = {this.state.chatType} sendMessageInGroup = {this.sendMessageInGroup} shareFile = {this.shareFile} downloadFile={this.downloadFile} /> ); //Load participant screen renderDetailsScreen = () => ( <EnxParticipantScreen onBack={() => this.setActionMethodForOutAnimation()} /> ); //Load More screen renderMoreScreen = () => ( <EnxMoreScreen ref={this.moreRef} onAction = {this.handleEventsAction} onBack={() => this.setActionMethodForOutAnimation()} /> ); //Load Room Setting Page renderRoomSetting = () => ( <EnxRoomSetting onBack={() => this.setActionMethodForOutAnimation()} /> ); renderQNAScreen = () => ( <EnxQnaScreen onBack={() => this.setActionMethodForOutAnimation()} ref={this.qnaRef} qnaData={this.state.qnaList} onQnaAction={this.qnaAction}/> ); renderPollingScreen = () => ( <EnxPollingScreen onBack={() => this.setActionMethodForOutAnimation()} ref={this.pollRef} pollData={this.state.pollsList} onNavigateToCreatePoll={() => this.setActionMethodForInAnimation('createPoll')} // Open Create Poll onPollAction={this.pollAction} /> ); updatePoll=(pollType,data) =>{ if(pollType=='poll-response'){ const updatedPollsList = this.updatePollList(this.state.pollsList, data); this.setState({ pollsList: updatedPollsList }, () => { if (this.pollRef.current) { this.pollRef.current.updateAction(this.state.pollsList); } }); }else if(pollType=='poll-start'){ console.log('stat poll1',data) let updatedData= this.convertPollData(data) this.setState({ isShowAnswerDialog:true, question:data.question, duration:Number(data.duration), answers:updatedData.answers, pollData:updatedData }) }else if(pollType=='poll-extend'){ if (this.pollAnswerRef.current) { this.pollAnswerRef.current.increaseTime(Number(data.extended_duration)); } }else if(pollType=='poll-stop'){ this.handleCancel() }else if(pollType=='poll-data'){ let resultData= this.getStructuredData(data) let pollData = {question:data.question,data:resultData}; this.setState({pollResultData : pollData, isShowResultDialog:true }) } } convertPollData(input) { const answers = Object.keys(input.options).map((key, index) => ({ id: `opt${index + 1}`, option: input.options[key], })); return { question: input.question, answers: answers, duration: Number(input.duration), id: input.id, timestamp: input.timestamp, userName: input.user_name, userRef: input.user_ref, }; } // Function to update poll list updatePollList(pollsList, response) { return pollsList.map((poll) => { if (poll.id == response.id) { const updatedData = poll.data.map((item) => { console.log("matchID",` "${item.option}" ${response.optionSelectedValue}`) if (item.option === response.optionSelectedValue) { return { ...item, votes: item.votes + 1 }; } return item; }); const totalVotes = updatedData.reduce((sum, item) => sum + item.votes, 0); const dataWithPercentages = updatedData.map((item) => ({ ...item, percentage: totalVotes > 0 ? ((item.votes / totalVotes) * 100).toFixed(2) : 0, })); return { ...poll, data: dataWithPercentages, total_result: totalVotes, }; } return poll; }); } pollAction= (data,polltype) => { if(polltype=='poll-start'){ // {"type":"poll-start","data":{"duration":"50","question":"Android","id":-584306009,"options":{"opt1":"java","opt2":"kotlin"},"result":{"opt1":0,"opt2":0},"total_result":0,"initialDuration":"50"}} // {"question":"Bzzg","data":[{"option":"Bxzv","votes":0,"percentage":0},{"option":"Dbxb","votes":0,"percentage":0}],"duration":"585","expanded":true} //Generate dynamic keys (opt1, opt2, opt3, etc.) and map to result let transformedData = { result: {}, options:{} }; const originalData = data.data; // Loop through the data and dynamically create "optX" keys originalData.forEach((item, index) => { // Create dynamic option names: opt1, opt2, opt3, etc. const optionKey = `opt${index + 1}`; transformedData.options[optionKey] = item.option; // Assign option name (java, kotlin, etc.) transformedData.result[optionKey] = item.votes; // Assign votes for each option }); transformedData['duration']=data.duration transformedData['question']=data.question transformedData['total_result']=0 transformedData['initialDuration']=data.duration transformedData['id']=data.id let pollData = { type:polltype , data:transformedData }; console.log(pollData); Enx.sendUserData(pollData,true,[]) this.setState(prevState => { const updatedList = prevState.pollsList.map(poll => poll.id === data.id ? { ...poll, status: "stopPoll" } : poll ); return { pollsList: updatedList }; }, () => { // This callback ensures state is updated if (this.pollRef.current) { this.pollRef.current.updateAction(this.state.pollsList); } }); }else if(polltype=='poll-stop'){ let pollData = { type:polltype , data:{id:data.id} }; Enx.sendUserData(pollData,true,[]) this.setState(prevState => { const updatedList = prevState.pollsList.map(poll => poll.id === data.id ? { ...poll, status: "Completed" } : poll ); return { pollsList: updatedList }; }, () => { // This callback ensures state is updated if (this.pollRef.current) { this.pollRef.current.updateAction(this.state.pollsList); } }); }else if(polltype=='extend-duration'){ let pollData = { type:'poll-extend' , id:data.id, extended_duration:10 }; Enx.sendUserData(pollData,true,[]) }else if(polltype=='publish-result'){ const transformedPollData = this.transformPollForResultData(data); let pollData = { type:'poll-data' , data:transformedPollData, }; Enx.sendUserData(pollData,true,[]) }else if(polltype=='repoll'){ const newPoll = { ...data, // Replace with your current poll data id: Date.now(), // Generate a new unique ID status: "stopPoll", // Reset status to default data: data.data.map((item) => ({ ...item, votes: 0, // Reset votes percentage: "0.00", // Reset percentage })), total_result: 0, // Reset total votes count expanded: true, // Mark this poll as expanded by default }; let transformedData = { result: {}, options:{} }; console.log("start poll :",` "${JSON.stringify(newPoll.data)}" ${polltype}`) // Loop through the data and dynamically create "optX" keys newPoll.data.forEach((item, index) => { // Create dynamic option names: opt1, opt2, opt3, etc. const optionKey = `opt${index + 1}`; transformedData.options[optionKey] = item.option; // Assign option name (java, kotlin, etc.) transformedData.result[optionKey] = 0; // Assign votes for each option }); transformedData['duration']=newPoll.duration transformedData['question']=newPoll.question transformedData['total_result']=0 transformedData['initialDuration']=newPoll.duration transformedData['id']=newPoll.id let pollData = { type:'poll-start' , data:transformedData }; console.log(pollData); Enx.sendUserData(pollData,true,[]) this.setState((prevState) => ({ pollsList: prevState.pollsList .map((poll, index) => ({ ...poll, expanded: false, // Collapse all existing polls })) .concat(newPoll), // Add the new poll at the end }),() => { // This callback ensures state is updated if (this.pollRef.current) { this.pollRef.current.updateAction(this.state.pollsList); } }); // this.setState(prevState => { // const updatedList = prevState.pollsList.map(poll => // poll.id === data.id ? { ...poll, status: "stopPoll" } : poll // ); // return { pollsList: updatedList }; // }, () => { // // This callback ensures state is updated // if (this.pollRef.current) { // this.pollRef.current.updateAction(this.state.pollsList); // } // }); } } qnaAction=(qnadata,qnatype)=>{ switch(qnatype){ case 'new-question': qnadata.qna.clientID=this.state.selfClientId qnadata.qna.username=this.state.selfUserName qnadata.qna.user_ref=this.state.selfUserRef //console.log("qnaType3", JSON.stringify(data)); console.log("qnaType2",""+JSON.stringify(qnadata)+"") let clientIds = []; Enx.getUserList(data => { for (var i = 0; i < data.length; i++) { if (data[i].role === 'moderator') { if (data[i].clientId !== this.state.selfClientId) { clientIds.push(data[i].clientId); } } } if(clientIds.length>0){ this.saveLocallyQnAData(qnadata) Enx.sendUserData(qnadata,false,clientIds) } // this.state.localStreamId = status; }); break; case 'answer_declined': let qnaDeclined={ type:'answer_declined', qna:{ id:qnadata.id, private:false, status:'D', ans:{ id:Date.now().toString(), timestamp:Date.now().toString(), private:false, note:'Declined', clientID:this.state.selfClientId, username:this.state.selfUserName, user_ref:this.state.selfUserRef, } } } Enx.sendUserData(qnaDeclined, true, []); this.updateLocallyQnAData(qnaDeclined,'answer_declined') break; case 'answered_live': let qnaObject={ type:'answered_live', qna:{ id:qnadata.id, private:false, status:'A', ans:{ id:Date.now().toString(), timestamp:Date.now().toString(), private:false, note:'Answered on live call', clientID:this.state.selfClientId, username:this.state.selfUserName, user_ref:this.state.selfUserRef, } } } Enx.sendUserData(qnaObject, true, []); this.updateLocallyQnAData(qnaObject,"answered_live") break; case 'answered_typed': this.setState({isShowTypeAnswerDialog:true, typeQnaObject:qnadata}) break; case 'delete-question': Enx.sendUserData(qnadata,true,[]) let qnaId = qnadata.qnaId; console.log("ide",qnadata) this.removeItemById(qnaId) break; } } removeItemById = (idToRemove) => { const idToRemoveString = String(idToRemove); this.setState((prevState) => { // Create a new copy of the qnaList const qnaList = [...prevState.qnaList]; const index = qnaList.findIndex(item => item.qna.id === idToRemoveString); if (index !== -1) { // Remove the item at the found index qnaList.splice(index, 1); } // After updating state, call the reference method if (this.qnaRef.current) { this.qnaRef.current.updateAction(qnaList,this.state.isModerator); // Pass the updated qnaList } // Return the updated state return { qnaList }; }); }; updateLocallyQnAData = (qnaData, type) => { console.log("updateQna", JSON.stringify(qnaData), type); // Use the spread operator to create a new array for immutability const updatedQnaList = this.state.qnaList.map((qnaItem) => { if (qnaItem.qna.id === qnaData.qna.id) { // Conditionally rename `note` to `ans` only if the type is `answer_declined` const answerEntry = { ...qnaData.qna.ans, ...(type === 'answer_declined'|| type === 'answered_live' ? { ans: qnaData.qna.ans.note, note: undefined } : {}) }; let finalobject ={ id:qnaData.qna.id, timestamp:qnaItem.qna.timestamp, question:qnaItem.qna.question, status:qnaItem.qna.status, clientID:qnaItem.qna.clientID, username:qnaItem.qna.username, user_ref:qnaItem.qna.user_ref, isInitiator:false, ans: [...qnaItem.qna.answer, answerEntry] } this.setCustomData(true,finalobject,qnaData.qna.id) // Return updated item with new answer and status return { ...qnaItem, qna: { ...qnaItem.qna, status: qnaData.qna.status, // Update status answer: [...qnaItem.qna.answer, answerEntry] // Append new answer } }; } return qnaItem; // Return unmodified item if ID doesn't match }); // Update the state with the modified list this.setState({ qnaList: updatedQnaList }, () => { // Use the updated qnaList directly after state update if (this.qnaRef.current) { this.qnaRef.current.updateAction(updatedQnaList, this.state.isModerator); } }); }; saveLocallyQnAData=(qnaData)=>{ // Convert input data to the required format and add to the list this.setState((prevState) => ({ qnaList: [ ...prevState.qnaList, { "type": qnaData.type, "qna": { ...qnaData.qna, "answer": [] // Add 'answer' key as an empty array } } ] })); let qna={ id:qnaData.qna.id, timestamp:qnaData.qna.timestamp, question:qnaData.qna.question, status:qnaData.qna.status, clientID:qnaData.qna.clientID, username:qnaData.qna.username, user_ref:qnaData.qna.user_ref } this.setCustomData(false,qna,qnaData.qna.id) if (this.qnaRef.current) { this.qnaRef.current.updateAction(this.state.qnaList,this.state.isModerator); // Call the method from EnxBottomView } } setCustomData=(isSave,data,id)=>{ console.log(isSave?"customDataSve":"customData",JSON.stringify(data)+isSave+id) let dataOption={ scope:"session", broadcast:"all", query:"qna."+id } if(isSave){ Enx.saveCustomData(dataOption,data) } else{ Enx.setCustomData(dataOption,data) } } updateQnaData=(data,type)=>{ console.log("answer2",JSON.stringify(data)) switch(type){ case 'answered_live': case 'answer_declined': let ansData = data.ans; let updateAnsData={ user_ref:ansData.user_ref, clientID:ansData.clientID, username:ansData.username, timestamp:ansData.timestamp, ans:ansData.note, id:ansData.id, } this.setState((prevState) => { const updatedQnaList = prevState.qnaList.map(item => { if (item.qna.id === data.id) { // Use a Map to filter out duplicates based on the `id` const answersMap = new Map(); // Update the existing answers and add them to the Map item.qna.answer.forEach(answer => { answersMap.set(answer.id, answer); }); // Add or update the current answer in the Map answersMap.set(updateAnsData.id, updateAnsData); // Convert Map values back to an array const updatedAnswers = Array.from(answersMap.values()); return { ...item, qna: { ...item.qna, status: data.status, answer: updatedAnswers } }; } return item; }); return { qnaList: updatedQnaList }; }, () => { // The state has been updated, now you can call updateAction console.log("test2", JSON.stringify(this.state.qnaList)); if (this.qnaRef.current) { this.qnaRef.current.updateAction(this.state.qnaList,this.state.isModerator); } }); break; case 'answered_typed': let typeAnsData = data.ans; this.setState((prevState) => { const updatedQnaList = prevState.qnaList.map(item => { if (item.qna.id === data.id) { return { ...item, qna: { ...item.qna, status: data.status, answer: [...item.qna.answer, typeAnsData] } }; } return item; }); return { qnaList: updatedQnaList }; }, () => { // The state has been updated, now you can call updateAction if (this.qnaRef.current) { this.qnaRef.current.updateAction(this.state.qnaList,this.state.isModerator); } }); break; case 'delete-question': this.removeItemById(data.qnaId) break; case 'new-question': // {"question":"Testing","user_ref":"Raj", // "clientID":"62ca4c98-61b1-425a-b6a7-9b3f33353708", // "username":"Raj","timestamp":"1736326952349", // "status":"O","id":"1736326952349"} this.setState((prevState) => ({ qnaList: [ ...prevState.qnaList, { "type": type, "qna": { ...data, "answer": [] // Add 'answer' key as an empty array } } ] }), () => { console.log('latestData', JSON.stringify(this.state.qnaList)); // Correct usage if (this.qnaRef.current) { this.qnaRef.current.updateAction(this.state.qnaList, this.state.isModerator); // Correct usage } }); break; } } transformPollForResultData=(poll) =>{ // Dynamically generate options and results const options = {}; const result = {}; poll.data.forEach((item, index) => { const key = `opt${index + 1}`; options[key] = item.option; // Map options result[key] = item.votes; // Map votes }); // Create the final transformed object return { question: poll.question, status: 'P', total_result: poll.total_result, id: poll.id.toString(), timestamp: new Date().getTime().toString(), // Assign the current timestamp dynamically conf_num: this.state.confNumber, // Example conf_num; replace if needed options: options, result: result }; } renderCreatePollingScreen = () => ( <EnxCreatePollScreen onBack={() => {this.setActionMethodForInAnimation('Polling-Page') }} onCreatePoll={this.createPoll} /> ); createPoll= (data) => { console.log("create poll :",` "${JSON.stringify(data)}"`) this.handleNewPoll(data) this.setActionMethodForInAnimation('Polling-Page') } // Method to handle a new poll received handleNewPoll = (pollData) => { const { options, result, question,duration ,id,status,total_result} = pollData; // Convert options object into an array const optionsArray = Object.keys(options).map((key) => ({ option: options[key], votes: result[key], percentage: 0, // Set initial percentage to 0 })); // Prepare the new poll object const newPoll = { question: question, data: optionsArray, duration:duration, id:id, status:status, total_result:total_result, expanded: true, // Set expanded to true for the newly added poll }; // Add the new poll and set expanded to false for the rest this.setState((prevState) => ({ pollsList: prevState.pollsList.map((poll, index) => ({ ...poll, expanded: index === prevState.pollsList.length ? true : false, // Only the last added poll is expanded })).concat(newPoll), // Add the new poll at the end })); Alert.alert('Poll Created', `Question: "${question}"\nOptions: ${JSON.stringify(options)}\nResult: ${JSON.stringify(result)}\nTimestamp: ${Date.now()}`); }; // Render the overlays based on current screen renderOverlay = () => { const { currentOverlay } = this.state; switch (currentOverlay) { case 'confirmation': return this.renderConfirmationScreen(); case 'chat': return this.renderChatScreen(); case 'details': return this.renderDetailsScreen(); case 'more' : return this.renderMoreScreen(); case 'Room-Setting' : return this.renderRoomSetting(); case 'QNA-Page' : return this.renderQNAScreen(); case 'Polling-Page' : return this.renderPollingScreen(); case 'createPoll' : return this.renderCreatePollingScreen(); default: return null; } } getNumColumns = (length) => { if (length < 2) return 1; if (length === 2) return 2; if (length === 3) return 3; if (length === 4) return 2; // 2 columns for 4 items return 3; // 3 columns for more than 4 items }; downloadFile = (index) => { try{ console.log("index",index) if(this.state.chatType==='group'){ console.log('download file from group chat',this.state.groupChatModel[index].jsondata) Enx.downloadFile(this.state.groupChatModel[index].jsondata,true) }else{ console.log('download file from private chat',this.state.groupChatModel[index].jsondata) Enx.downloadFile(this.state.privateChatModel[index].jsondata,true) } }catch(err){ console.log("Error: ",err.message) } } shareFile = (option) =>{ var clientList = [] if(option.type === 'group'){ console.log('dsfsdfs') Enx.sendFiles(true,clientList) }else{ clientList.push(option.clientId) Enx.sendFiles(false,clientList) } } sendMessageInGroup = (option) => { if(option.type === 'group'){ var clientList = [] privateChatModel= { broadcast: true, sender: this.state.selfName, senderId:'', type:option.type, message:option.msg, timestamp: Date.now(), recipients:[], isReceived:false, fileName:'', jsondata:'', } var tempArray = [] if(this.state.groupChatModel.length>0){ tempArray = this.state.groupChatModel } tempArray.push(privateChatModel) this.setState({groupChatModel:tempArray}) Enx.sendMessage(option.msg,true,clientList) }else{ console.log("=========== private chat =========================",option) var clientList = [] clientList.push(option.clientId) privateChatModel= { broadcast: false, sender: this.state.selfName, senderId:'', type:option.type, message:option.msg, timestamp: Date.now(), recipients:clientList, isReceived:false, fileName:'', jsondata:'', } console.log("=========== private chat1 =========================",clientList) Enx.sendMessage(option.msg,false,clientList) // Add chat to chatlist var tempArray = [] if(this.state.chatModelList.length>0){ tempArray = this.state.chatModelList } for(var i = 0 ; i<tempArray.length;i++){ var chatModel = tempArray[i] if(chatModel.headerClientId ===option.clientId){ chatModel.chatList.push(privateChatModel) this.setState({ privateChatModel:chatModel.chatList }) } } this.setState({ chatModelList: tempArray }) } this.forceUpdate() } createActiveStreamView() { try { if (this.state.mActiveStreamId != null) { return ( <View style={{ flex: 1, }}> <TouchableWithoutFeedback onPress={() => { this.setState({ isToolBarsVisible: !this.state.isToolBarsVisible }) }} > <EnxPlayerView style={{ flex: 1 }} key={String(this.state.mActiveStreamId)} streamId={String(this.state.mActiveStreamId)} isLocal="remote" /> </TouchableWithoutFeedback> </View> ); } } catch (err) { console.log("Error : ", err.message) } } render() { const { animationType } = this.state; // Apply either horizontal or vertical transform const { route } = this.props; const obj = pick(this.props, ['token']); const numColumns = this.getNumColumns(this.state.activeTalkerStreams.length); const isLandscape = this.state.windowWidth > this.state.windowHeight; const slideTransform = animationType === "horizontal" ? { transform: [{ translateX: this.state.slideAnim }] } : { transform: [{ translateY: this.state.slideAnimHeight }] }; return ( <View style={styles.container}> {/* Main plugin screen is static and always visible */} <View style={this.state.isChatViewVisible?styles.videoContainer1:styles.videoContainer}> {/* Render your main EnxVideoScreen here, it's permanent */} <EnxRoom token={obj.token} eventHandlers={this.roomEventHandlers} localInfo={this.getLocalStream} roomInfo={this.state.roomInfo} advanceOptionsInfo={this.state.advanceOptions}> { !this.state.isMoreVisible ? ( <View style={{ position: 'relative' }}> <EnxStream key={this.state.isConnected ? Date.now() : 'stream'} style={{ right:1, width:100, height: 100, }} eventHandlers={this.streamEventHandlers} isPreview = {false} /> {/* Button overlay */} { (this.state.role!=='moderator'&&this.state.isLectureMode)? <TouchableOpacity style={{ position: 'absolute', // Position from the top of the stream view right: 5, // Position from the right of the stream view zIndex: 1, // Ensure the button is on top of the stream padding: 5, // Rounded button }} onPress={() => { this.handleFloorRequest() }} > <Image source={this.state.participantFloorAction==1?require("../image_asset/hand_raised.png"):require("../image_asset/raise_hand.png")} style={styles.raiseHandImg} /> </TouchableOpacity> :null} </View>):<View></View>} </EnxRoom> </View> {/* At View */} <View style={{ flex: 1 }}> {this.state.mActiveStreamId == null ? ( // Full-screen active talker streams when mActiveStreamId is null <View style={{ flex: 1, width: '100%' }}> {this.state.activeTalkerStreams.length > 0 ? ( Dimensions.get('window').height < Dimensions.get('window').width ? ( // Landscape mode <FlatList key={`list-${numColumns}`} // Dynamic key for re-rendering extraData={this.state.refresh} data={this.state.activeTalkerStreams} contentContainerStyle={styles.flexList} renderItem={this.renderItem1} numColumns={numColumns} // Use calculated columns showsHorizontalScrollIndicator={false} // Optional /> ) : this.state.activeTalkerStreams.length < 3 ? ( // Portrait mode with less than 3 items <FlatList key={'_'} extraData={this.state.refresh} data={this.state.activeTalkerStreams} contentContainerStyle={styles.flexList} renderItem={this.renderItem} numColumns={1} /> ) : ( // Portrait mode with 3 or more items <FlatList key={'#'} extraData={this.state.refresh} data={this.state.activeTalkerStreams} contentContainerStyle={styles.flexList} renderItem={this.renderItem} numColumns={2} /> ) ) : ( // No streams available <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Text style={styles.waitMsg}> Please wait for others to join </Text> </View> )} </View> ) : ( // Split-screen layout when mActiveStreamId is not null isLandscape? <View style={{ flexDirection: 'row', }}> {/* Share View (70%) */} <View style={{ height:'100%', width: '70%', backgroundColor: 'grey' }}> {this.createActiveStreamView()} </View> {/* Active Talker Streams (30%) */} <View style={{ height: '100%', width: '30%' }}> {this.state.activeTalkerStreams.length > 0 ? ( Dimensions.get('window').height > Dimensions.get('window').width ? ( // Landscape mode <FlatList key={`list-${numColumns}`} // Dynamic key for re-rendering extraData={this.state.refresh} data={this.state.activeTalkerStreams} contentContainerStyle={styles.flexList} renderItem={this.renderItem1} numColumns={numColumns} // Use calculated columns showsHorizontalScrollIndicator={false} // Optional /> ) : this.state.activeTalkerStreams.length < 3 ? ( // Portrait mode with less than 3 items <FlatList key={'_'} extraData={this.state.refresh} data={this.state.activeTalkerStreams} contentContainerStyle={styles.flexList} renderItem={this.renderItem} numColumns={1} /> ) : ( // Portrait mode with 3 or more items <FlatList key={'#'} extraData={this.state.refresh} data={this.state.activeTalkerStreams} contentContainerStyle={styles.flexList} renderItem={this.renderItem} numColumns={2} /> ) ) : ( // No streams available <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Text style={styles.waitMsg}> Please wait for others to join </Text> </View> )} </View> </View>: <> {/* Share View (70%) */} <View style={{ height:'70%', width: '100%', backgroundColor: 'grey' }}> {this.createActiveStreamView()} </View> {/* Active Talker Streams (30%) */} <View style={{ height: '30%', width: '100%' ,flex: 1}}> {this.state.activeTalkerStreams.length > 0 ? ( this.state.screenWindowHeight > Dimensions.get('window').width ? ( // Landscape mode <FlatList key={`list-${numColumns}`} // Dynamic key for re-rendering extraData={this.state.refresh} data={this.state.activeTalkerStreams} contentContainerStyle={styles.flexList} renderItem={this.renderItem1} numColumns={numColumns} // Use calculated columns showsHorizontalScrollIndicator={false} // Optional /> ) : this.state.activeTalkerStreams.length < 3 ? ( // Portrait mode with less than 3 items <FlatList key={'_'} extraData={this.state.refresh} data={this.state.activeTalkerStreams} contentContainerStyle={styles.flexList} renderItem={this.renderItem} numColumns={1} /> ) : ( // Portrait mode with 3 or more items <FlatList key={'#'} extraData={this.state.refresh} data={this.state.activeTalkerStreams} contentContainerStyle={styles.flexList} renderItem={this.renderItem} numColumns={2} /> ) ) : ( // No streams available <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Text style={styles.waitMsg}> Please wait for others to join </Text> </View> )} </View> </> )} </View> {/* Main controls to trigger overlay screens, This we need to remove after other apis done */} { this.state.recordingCheck? <Blink duration={500} style={{position: 'absolute', alignSelf:'center', width:'auto', top:0,marginTop:15, zIndex:50}}> <View > <TouchableHighlight underlayColor="transparent" > <Image source={this.state.recordingImage} style={styles.inlineImg} /> </TouchableHighlight> </View> </Blink> :null } {this.lobbyRequestDialog()} {this.roomSettingDialog()} {this.shareRequestDialog()} {this.pollResultDialog()} {this.pollAnswerDialog()} {this.typeAnswerDialog()} {this.state.isSwitchMedia?this.switchMediaD