sfm-uikit-react-native
Version:
It is a react native component for SmartFloMeet users.
1,511 lines (1,375 loc) • 149 kB
JavaScript
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