import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:diplomaticquarterapp/pages/conference/zoom/jwt.dart'; import 'package:diplomaticquarterapp/pages/conference/zoom/video_view.dart'; import 'package:events_emitter/events_emitter.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_zoom_videosdk/native/zoom_videosdk.dart'; import 'package:flutter_zoom_videosdk/native/zoom_videosdk_cmd_channel.dart'; import 'package:flutter_zoom_videosdk/native/zoom_videosdk_event_listener.dart'; import 'package:flutter_zoom_videosdk/native/zoom_videosdk_live_transcription_message_info.dart'; import 'package:flutter_zoom_videosdk/native/zoom_videosdk_user.dart'; import 'package:google_fonts/google_fonts.dart'; // import '../components/video_view.dart'; // import '../components/comment_list.dart'; // import 'intro_screen.dart'; // import 'join_screen.dart'; class CallScreen extends StatefulHookWidget { const CallScreen({Key? key}) : super(key: key); @override State createState() => _CallScreenState(); } class _CallScreenState extends State { static TextEditingController changeNameController = TextEditingController(); double opacityLevel = 1.0; String currentCameraDeviceId = "1"; void _changeOpacity() { setState(() => opacityLevel = opacityLevel == 0 ? 1.0 : 0.0); } @override Widget build(BuildContext context) { var zoom = ZoomVideoSdk(); var eventListener = ZoomVideoSdkEventListener(); var isInSession = useState(false); var sessionName = useState(''); var sessionPassword = useState(''); var users = useState([]); var fullScreenUser = useState(null); var sharingUser = useState(null); var videoInfo = useState(''); var isSharing = useState(false); var isMuted = useState(true); var isVideoOn = useState(false); var isSpeakerOn = useState(false); var isRenameModalVisible = useState(false); var isRecordingStarted = useState(false); var isMicOriginalOn = useState(false); var isMounted = useIsMounted(); var audioStatusFlag = useState(false); var videoStatusFlag = useState(false); var userNameFlag = useState(false); var userShareStatusFlag = useState(false); var isReceiveSpokenLanguageContentEnabled = useState(false); var isVideoMirrored = useState(false); var isOriginalAspectRatio = useState(false); //hide status bar SystemChrome.setEnabledSystemUIMode(SystemUiMode.leanBack); var circleButtonSize = 65.0; Color backgroundColor = const Color(0xFF232323); Color buttonBackgroundColor = const Color.fromRGBO(0, 0, 0, 0.6); Color chatTextColor = const Color(0xFFAAAAAA); Widget moreOptions; final args = ModalRoute.of(context)!.settings.arguments as CallArguments; useEffect(() { Future.microtask(() async { var token = generateJwt(args.sessionName, args.role); try { Map SDKaudioOptions = {"connect": true, "mute": true, "autoAdjustSpeakerVolume": false}; Map SDKvideoOptions = { "localVideoOn": true, }; JoinSessionConfig joinSession = JoinSessionConfig( sessionName: args.sessionName, sessionPassword: args.sessionPwd, token: token, userName: args.displayName, audioOptions: SDKaudioOptions, videoOptions: SDKvideoOptions, sessionIdleTimeoutMins: int.parse(args.sessionIdleTimeoutMins), ); await zoom.joinSession(joinSession); } catch (e) { print(e); const AlertDialog( title: Text("Error"), content: Text("Failed to join the session"), ); Future.delayed(const Duration(milliseconds: 1000)).asStream().listen((event) { Navigator.pop(context); // Navigator.popAndPushNamed( // context, // "Join", // arguments: JoinArguments(args.isJoin, sessionName.value, sessionPassword.value, args.displayName, args.sessionIdleTimeoutMins, args.role), // ); }); } }); return null; }, []); useEffect(() { updateVideoInfo() { Timer timer = Timer.periodic(const Duration(milliseconds: 1000), (time) async { if (!isMounted()) return; bool? videoOn = false; videoOn = await fullScreenUser.value?.videoStatus?.isOn(); // Video statistic info doesn't update when there's no remote users if (fullScreenUser.value == null || (videoOn != null && videoOn == false) || users.value.length < 2) { time.cancel(); videoInfo.value = ""; return; } var fps = isSharing.value ? await fullScreenUser.value?.shareStatisticInfo?.getFps() : await fullScreenUser.value?.videoStatisticInfo?.getFps(); var height = isSharing.value ? await fullScreenUser.value?.shareStatisticInfo?.getHeight() : await fullScreenUser.value?.videoStatisticInfo?.getHeight(); var width = isSharing.value ? await fullScreenUser.value?.shareStatisticInfo?.getWidth() : await fullScreenUser.value?.videoStatisticInfo?.getWidth(); videoInfo.value = ("${width}x$height ${fps}FPS"); }); } updateVideoInfo(); return null; }); useEffect(() { eventListener.addEventListener(); EventEmitter emitter = eventListener.eventEmitter; final sessionJoinListener = emitter.on(EventType.onSessionJoin, (sessionUser) async { isInSession.value = true; zoom.session.getSessionName().then((value) => sessionName.value = value!); sessionPassword.value = await zoom.session.getSessionPassword(); ZoomVideoSdkUser mySelf = ZoomVideoSdkUser.fromJson(jsonDecode(sessionUser.toString())); List? remoteUsers = await zoom.session.getRemoteUsers(); var muted = await mySelf.audioStatus?.isMuted(); var videoOn = await mySelf.videoStatus?.isOn(); var speakerOn = await zoom.audioHelper.getSpeakerStatus(); // fullScreenUser.value = mySelf; fullScreenUser.value = remoteUsers!.isNotEmpty ? remoteUsers![0] : mySelf; remoteUsers?.insert(0, mySelf); users.value = remoteUsers!; isMuted.value = muted!; isSpeakerOn.value = speakerOn; isVideoOn.value = videoOn!; users.value = remoteUsers; isReceiveSpokenLanguageContentEnabled.value = await zoom.liveTranscriptionHelper.isReceiveSpokenLanguageContentEnabled(); }); final sessionLeaveListener = emitter.on(EventType.onSessionLeave, (data) async { isInSession.value = false; users.value = []; fullScreenUser.value = null; await zoom.leaveSession(false); Navigator.pop(context); }); final sessionNeedPasswordListener = emitter.on(EventType.onSessionNeedPassword, (data) async { showDialog( context: context, builder: (BuildContext context) => AlertDialog( title: const Text('Session Need Password'), content: const Text('Password is required'), actions: [ TextButton( onPressed: () async => { // Navigator.popAndPushNamed(context, 'Join', arguments: JoinArguments(args.isJoin, args.sessionName, "", args.displayName, args.sessionIdleTimeoutMins, args.role)), Navigator.pop(context), await zoom.leaveSession(false), }, child: const Text('OK'), ), ], ), ); }); final sessionPasswordWrongListener = emitter.on(EventType.onSessionPasswordWrong, (data) async { showDialog( context: context, builder: (BuildContext context) => AlertDialog( title: const Text('Session Password Incorrect'), content: const Text('Password is wrong'), actions: [ TextButton( onPressed: () async => { // Navigator.popAndPushNamed(context, 'Join', arguments: JoinArguments(args.isJoin, args.sessionName, "", args.displayName, args.sessionIdleTimeoutMins, args.role)), Navigator.pop(context), await zoom.leaveSession(false), }, child: const Text('OK'), ), ], ), ); }); final userVideoStatusChangedListener = emitter.on(EventType.onUserVideoStatusChanged, (data) async { data = data as Map; ZoomVideoSdkUser? mySelf = await zoom.session.getMySelf(); var userListJson = jsonDecode(data['changedUsers']) as List; List userList = userListJson.map((userJson) => ZoomVideoSdkUser.fromJson(userJson)).toList(); for (var user in userList) { { if (user.userId == mySelf?.userId) { mySelf?.videoStatus?.isOn().then((on) => isVideoOn.value = on); } } } videoStatusFlag.value = !videoStatusFlag.value; }); final userAudioStatusChangedListener = emitter.on(EventType.onUserAudioStatusChanged, (data) async { data = data as Map; ZoomVideoSdkUser? mySelf = await zoom.session.getMySelf(); var userListJson = jsonDecode(data['changedUsers']) as List; List userList = userListJson.map((userJson) => ZoomVideoSdkUser.fromJson(userJson)).toList(); for (var user in userList) { { if (user.userId == mySelf?.userId) { mySelf?.audioStatus?.isMuted().then((muted) => isMuted.value = muted); } } } audioStatusFlag.value = !audioStatusFlag.value; }); final userShareStatusChangeListener = emitter.on(EventType.onUserShareStatusChanged, (data) async { data = data as Map; ZoomVideoSdkUser? mySelf = await zoom.session.getMySelf(); ZoomVideoSdkUser shareUser = ZoomVideoSdkUser.fromJson(jsonDecode(data['user'].toString())); if (data['status'] == ShareStatus.Start) { sharingUser.value = shareUser; fullScreenUser.value = shareUser; isSharing.value = (shareUser.userId == mySelf?.userId); } else { sharingUser.value = null; isSharing.value = false; fullScreenUser.value = mySelf; } userShareStatusFlag.value = !userShareStatusFlag.value; }); final userJoinListener = emitter.on(EventType.onUserJoin, (data) async { if (!isMounted()) return; data = data as Map; ZoomVideoSdkUser? mySelf = await zoom.session.getMySelf(); var userListJson = jsonDecode(data['remoteUsers']) as List; List remoteUserList = userListJson.map((userJson) => ZoomVideoSdkUser.fromJson(userJson)).toList(); remoteUserList.insert(0, mySelf!); users.value = remoteUserList; fullScreenUser.value = remoteUserList[1]; }); final userLeaveListener = emitter.on(EventType.onUserLeave, (data) async { if (!isMounted()) return; ZoomVideoSdkUser? mySelf = await zoom.session.getMySelf(); data = data as Map; var remoteUserListJson = jsonDecode(data['remoteUsers']) as List; List remoteUserList = remoteUserListJson.map((userJson) => ZoomVideoSdkUser.fromJson(userJson)).toList(); var leftUserListJson = jsonDecode(data['leftUsers']) as List; List leftUserLis = leftUserListJson.map((userJson) => ZoomVideoSdkUser.fromJson(userJson)).toList(); if (fullScreenUser.value != null) { for (var user in leftUserLis) { { if (fullScreenUser.value?.userId == user.userId) { fullScreenUser.value = mySelf; } } } } else { fullScreenUser.value = mySelf; } remoteUserList.add(mySelf!); users.value = remoteUserList; }); final userNameChangedListener = emitter.on(EventType.onUserNameChanged, (data) async { if (!isMounted()) return; data = data as Map; ZoomVideoSdkUser? changedUser = ZoomVideoSdkUser.fromJson(jsonDecode(data['changedUser'])); int index; for (var user in users.value) { if (user.userId == changedUser.userId) { index = users.value.indexOf(user); users.value[index] = changedUser; } } userNameFlag.value = !userNameFlag.value; }); final commandReceived = emitter.on(EventType.onCommandReceived, (data) async { data = data as Map; debugPrint("sender: ${ZoomVideoSdkUser.fromJson(jsonDecode(data['sender']))}, command: ${data['command']}"); }); final chatDeleteMessageNotify = emitter.on(EventType.onChatDeleteMessageNotify, (data) async { data = data as Map; debugPrint("onChatDeleteMessageNotify: messageID: ${data['msgID']}, deleteBy: ${data['type']}"); }); final liveStreamStatusChangeListener = emitter.on(EventType.onLiveStreamStatusChanged, (data) async { data = data as Map; debugPrint("onLiveStreamStatusChanged: status: ${data['status']}"); }); final liveTranscriptionStatusChangeListener = emitter.on(EventType.onLiveTranscriptionStatus, (data) async { data = data as Map; debugPrint("onLiveTranscriptionStatus: status: ${data['status']}"); }); final cloudRecordingStatusListener = emitter.on(EventType.onCloudRecordingStatus, (data) async { data = data as Map; debugPrint("onCloudRecordingStatus: status: ${data['status']}"); ZoomVideoSdkUser? mySelf = await zoom.session.getMySelf(); if (data['status'] == RecordingStatus.Start) { if (mySelf != null && !mySelf.isHost!) { await zoom.acceptRecordingConsent(); // isRecordingStarted.value = true; // showDialog( // context: context, // builder: (BuildContext context) => AlertDialog( // content: const Text('The session is being recorded.'), // actions: [ // TextButton( // onPressed: () async { // await zoom.acceptRecordingConsent(); // // if (context.mounted) { // Navigator.pop(context); // // }; // }, // child: const Text('accept'), // ), // TextButton( // onPressed: () async { // String currentConsentType = await zoom.getRecordingConsentType(); // if (currentConsentType == ConsentType.ConsentType_Individual) { // await zoom.declineRecordingConsent(); // Navigator.pop(context); // } else { // await zoom.declineRecordingConsent(); // zoom.leaveSession(false); // // if (!context.mounted) return; // // Navigator.popAndPushNamed( // // context, // // "Join", // // arguments: JoinArguments(args.isJoin, sessionName.value, sessionPassword.value, args.displayName, args.sessionIdleTimeoutMins, args.role), // // ); // Navigator.pop(context); // } // }, // child: const Text('decline'), // ), // ], // ), // ); } isRecordingStarted.value = true; } else { isRecordingStarted.value = false; } }); final liveTranscriptionMsgInfoReceivedListener = emitter.on(EventType.onLiveTranscriptionMsgInfoReceived, (data) async { data = data as Map; ZoomVideoSdkLiveTranscriptionMessageInfo? messageInfo = ZoomVideoSdkLiveTranscriptionMessageInfo.fromJson(jsonDecode(data['messageInfo'])); debugPrint("onLiveTranscriptionMsgInfoReceived: content: ${messageInfo.messageContent}"); }); final inviteByPhoneStatusListener = emitter.on(EventType.onInviteByPhoneStatus, (data) async { data = data as Map; debugPrint("onInviteByPhoneStatus: status: ${data['status']}, reason: ${data['reason']}"); }); final multiCameraStreamStatusChangedListener = emitter.on(EventType.onMultiCameraStreamStatusChanged, (data) async { data = data as Map; ZoomVideoSdkUser? changedUser = ZoomVideoSdkUser.fromJson(jsonDecode(data['changedUser'])); var status = data['status']; for (var user in users.value) { { if (changedUser.userId == user.userId) { if (status == MultiCameraStreamStatus.Joined) { user.hasMultiCamera = true; } else if (status == MultiCameraStreamStatus.Left) { user.hasMultiCamera = false; } } } } }); final requireSystemPermission = emitter.on(EventType.onRequireSystemPermission, (data) async { data = data as Map; ZoomVideoSdkUser? changedUser = ZoomVideoSdkUser.fromJson(jsonDecode(data['changedUser'])); var permissionType = data['permissionType']; switch (permissionType) { case SystemPermissionType.Camera: showDialog( context: context, builder: (BuildContext context) => AlertDialog( title: const Text("Can't Access Camera"), content: const Text("please turn on the toggle in system settings to grant permission"), actions: [ TextButton( onPressed: () => Navigator.pop(context, 'OK'), child: const Text('OK'), ), ], ), ); break; case SystemPermissionType.Microphone: showDialog( context: context, builder: (BuildContext context) => AlertDialog( title: const Text("Can't Access Microphone"), content: const Text("please turn on the toggle in system settings to grant permission"), actions: [ TextButton( onPressed: () => Navigator.pop(context, 'OK'), child: const Text('OK'), ), ], ), ); break; } }); final networkStatusChangeListener = emitter.on(EventType.onUserVideoNetworkStatusChanged, (data) async { data = data as Map; ZoomVideoSdkUser? networkUser = ZoomVideoSdkUser.fromJson(jsonDecode(data['user'])); if (data['status'] == NetworkStatus.Bad) { debugPrint("onUserVideoNetworkStatusChanged: status: ${data['status']}, user: ${networkUser.userName}"); } }); final eventErrorListener = emitter.on( EventType.onError, (data) async { data = data as Map; String errorType = data['errorType']; showDialog( context: context, builder: (BuildContext context) => AlertDialog( title: const Text("Error"), content: Text(errorType), actions: [ TextButton( onPressed: () => Navigator.pop(context, 'OK'), child: const Text('OK'), ), ], ), ); if (errorType == Errors.SessionJoinFailed || errorType == Errors.SessionDisconncting) { Timer( const Duration(milliseconds: 1000), () => Navigator.pop(context), // Navigator.popAndPushNamed( // context, // "Join", // arguments: JoinArguments(args.isJoin, sessionName.value, sessionPassword.value, args.displayName, args.sessionIdleTimeoutMins, args.role), // ), ); } }, ); final userRecordingConsentListener = emitter.on(EventType.onUserRecordingConsent, (data) async { data = data as Map; ZoomVideoSdkUser? user = ZoomVideoSdkUser.fromJson(jsonDecode(data['user'])); debugPrint('userRecordingConsentListener: user= ${user.userName}'); }); final callCRCDeviceStatusListener = emitter.on(EventType.onCallCRCDeviceStatusChanged, (data) async { data = data as Map; debugPrint('onCallCRCDeviceStatusChanged: status = ${data['status']}'); }); final originalLanguageMsgReceivedListener = emitter.on(EventType.onOriginalLanguageMsgReceived, (data) async { data = data as Map; ZoomVideoSdkLiveTranscriptionMessageInfo? messageInfo = ZoomVideoSdkLiveTranscriptionMessageInfo.fromJson(jsonDecode(data['messageInfo'])); debugPrint("onOriginalLanguageMsgReceived: content: ${messageInfo.messageContent}"); }); final chatPrivilegeChangedListener = emitter.on(EventType.onChatPrivilegeChanged, (data) async { data = data as Map; String type = data['privilege']; debugPrint('chatPrivilegeChangedListener: type= $type'); }); return () => { sessionJoinListener.cancel(), sessionLeaveListener.cancel(), sessionPasswordWrongListener.cancel(), sessionNeedPasswordListener.cancel(), userVideoStatusChangedListener.cancel(), userAudioStatusChangedListener.cancel(), userJoinListener.cancel(), userLeaveListener.cancel(), userNameChangedListener.cancel(), userShareStatusChangeListener.cancel(), liveStreamStatusChangeListener.cancel(), cloudRecordingStatusListener.cancel(), inviteByPhoneStatusListener.cancel(), eventErrorListener.cancel(), commandReceived.cancel(), chatDeleteMessageNotify.cancel(), liveTranscriptionStatusChangeListener.cancel(), liveTranscriptionMsgInfoReceivedListener.cancel(), multiCameraStreamStatusChangedListener.cancel(), requireSystemPermission.cancel(), userRecordingConsentListener.cancel(), networkStatusChangeListener.cancel(), callCRCDeviceStatusListener.cancel(), originalLanguageMsgReceivedListener.cancel(), chatPrivilegeChangedListener.cancel(), }; }, [zoom, users.value, isMounted]); void onPressAudio() async { ZoomVideoSdkUser? mySelf = await zoom.session.getMySelf(); if (mySelf != null) { final audioStatus = mySelf.audioStatus; if (audioStatus != null) { var muted = await audioStatus.isMuted(); if (muted) { await zoom.audioHelper.unMuteAudio(mySelf.userId); } else { await zoom.audioHelper.muteAudio(mySelf.userId); } } } } void onPressCameraChange() async { ZoomVideoSdkUser? mySelf = await zoom.session.getMySelf(); if (mySelf != null) { final videoStatus = mySelf.videoStatus; if (videoStatus != null) { if (currentCameraDeviceId == "1") { currentCameraDeviceId = "2"; await zoom.videoHelper.switchCamera("2"); } else { currentCameraDeviceId = "1"; await zoom.videoHelper.switchCamera("1"); } } } } void onPressVideo() async { ZoomVideoSdkUser? mySelf = await zoom.session.getMySelf(); if (mySelf != null) { final videoStatus = mySelf.videoStatus; if (videoStatus != null) { var videoOn = await videoStatus.isOn(); if (videoOn) { await zoom.videoHelper.stopVideo(); } else { await zoom.videoHelper.startVideo(); } } } } void onPressShare() async { var isOtherSharing = await zoom.shareHelper.isOtherSharing(); var isShareLocked = await zoom.shareHelper.isShareLocked(); if (isOtherSharing) { showDialog( context: context, builder: (BuildContext context) => AlertDialog( title: const Text("Error"), content: const Text('Other is sharing'), actions: [ TextButton( onPressed: () => Navigator.pop(context, 'OK'), child: const Text('OK'), ), ], ), ); } else if (isShareLocked) { showDialog( context: context, builder: (BuildContext context) => AlertDialog( title: const Text("Error"), content: const Text('Share is locked by host'), actions: [ TextButton( onPressed: () => Navigator.pop(context, 'OK'), child: const Text('OK'), ), ], ), ); } else if (isSharing.value) { zoom.shareHelper.stopShare(); } else { zoom.shareHelper.shareScreen(); } } moreOptions = Center( child: Stack( children: [ Visibility( visible: isRenameModalVisible.value, child: Row( mainAxisSize: MainAxisSize.min, children: [ Container( alignment: Alignment.bottomLeft, decoration: const BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(10)), color: Colors.white, ), width: MediaQuery.of(context).size.width - 130, height: 130, child: Center( child: (Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.only(top: 20, left: 20), child: Text( 'Change Name', style: GoogleFonts.lato( textStyle: const TextStyle( fontSize: 18, fontWeight: FontWeight.w500, ), ), ), ), Padding( padding: const EdgeInsets.only(top: 10, left: 20), child: SizedBox( width: MediaQuery.of(context).size.width - 230, child: TextField( onEditingComplete: () {}, autofocus: true, cursorColor: Colors.black, controller: changeNameController, decoration: InputDecoration( isDense: true, hintText: 'New name', hintStyle: TextStyle( fontSize: 14.0, color: chatTextColor, ), ), ), ), ), Padding( padding: const EdgeInsets.only(top: 15), child: Row( children: [ Padding( padding: const EdgeInsets.only(left: 40), child: InkWell( child: Text( 'Apply', style: GoogleFonts.lato( textStyle: const TextStyle( fontSize: 16, ), ), ), onTap: () async { if (fullScreenUser.value != null) { ZoomVideoSdkUser? mySelf = await zoom.session.getMySelf(); await zoom.userHelper.changeName((mySelf?.userId)!, changeNameController.text); changeNameController.clear(); } isRenameModalVisible.value = false; }, ), ), Padding( padding: const EdgeInsets.only(left: 40), child: InkWell( child: Text( 'Cancel', style: GoogleFonts.lato( textStyle: const TextStyle( fontSize: 16, ), ), ), onTap: () async { isRenameModalVisible.value = false; }, ), ) ], ), ) ], )), ), ), ], )), ], ), ); void onSelectedUserVolume(ZoomVideoSdkUser user) async { var isShareAudio = user.isSharing; bool canSetVolume = await user.canSetUserVolume(user.userId, isShareAudio); num userVolume; List options = [ ListTile( title: Text( 'Adjust Volume', style: GoogleFonts.lato( textStyle: const TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Colors.black, ), ), ), ), ListTile( title: Text( 'Current volume', style: GoogleFonts.lato( textStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.normal, color: Colors.black, ), ), ), onTap: () async => { debugPrint('user volume'), userVolume = await user.getUserVolume(user.userId, isShareAudio), debugPrint('user ${user.userName}\'s volume is ${userVolume!}'), }, ), ]; if (canSetVolume) { options.add( ListTile( title: Text( 'Volume up', style: GoogleFonts.lato( textStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.normal, color: Colors.black, ), ), ), onTap: () async => { userVolume = await user.getUserVolume(user.userId, isShareAudio), if (userVolume < 10) { await user.setUserVolume(user.userId, userVolume + 1, isShareAudio), } else { debugPrint("Cannot volume up."), } }, ), ); options.add( ListTile( title: Text( 'Volume down', style: GoogleFonts.lato( textStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.normal, color: Colors.black, ), ), ), onTap: () async => { userVolume = await user.getUserVolume(user.userId, isShareAudio), if (userVolume > 0) { await user.setUserVolume(user.userId, userVolume - 1, isShareAudio), } else { debugPrint("Cannot volume down."), } }, ), ); } showDialog( context: context, builder: (context) { return Dialog( elevation: 0.0, insetPadding: const EdgeInsets.symmetric(horizontal: 40), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), child: SizedBox( height: options.length * 58, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ ListView( shrinkWrap: true, children: ListTile.divideTiles( context: context, tiles: options, ).toList(), ), ], ), )); }); } Future onPressMore() async { ZoomVideoSdkUser? mySelf = await zoom.session.getMySelf(); bool isShareLocked = await zoom.shareHelper.isShareLocked(); bool canSwitchSpeaker = await zoom.audioHelper.canSwitchSpeaker(); bool canStartRecording = (await zoom.recordingHelper.canStartRecording()) == Errors.Success; var startLiveTranscription = (await zoom.liveTranscriptionHelper.getLiveTranscriptionStatus()) == LiveTranscriptionStatus.Start; bool canStartLiveTranscription = await zoom.liveTranscriptionHelper.canStartLiveTranscription(); bool canStartLiveStream = await zoom.liveStreamHelper.canStartLiveStream() == Errors.Success; debugPrint(await zoom.liveStreamHelper.canStartLiveStream()); bool startLiveStream = false; bool isHost = (mySelf != null) ? (await mySelf.getIsHost()) : false; isOriginalAspectRatio.value = await zoom.videoHelper.isOriginalAspectRatioEnabled(); bool canCallOutToCRC = await zoom.CRCHelper.isCRCEnabled(); List options = [ ListTile( title: Text( 'More', style: GoogleFonts.lato( textStyle: const TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Colors.black, ), ), ), ), ListTile( title: Text( 'is Support Virtual Background', style: GoogleFonts.lato( textStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.normal, color: Colors.black, ), ), ), onTap: () async => { debugPrint("${await zoom.virtualBackgroundHelper.isSupportVirtualBackground()}"), Navigator.of(context).pop(), }, ), ListTile( title: Text( 'Get Chat Privilege', style: GoogleFonts.lato( textStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.normal, color: Colors.black, ), ), ), onTap: () async => { debugPrint("Chat Privilege = ${await zoom.chatHelper.getChatPrivilege()}"), Navigator.of(context).pop(), }, ), ListTile( title: Text( 'Get Session Dial-in Number infos', style: GoogleFonts.lato( textStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.normal, color: Colors.black, ), ), ), onTap: () async => { debugPrint("session number = ${await zoom.session.getSessionNumber()}"), Navigator.of(context).pop(), }, ), ListTile( title: Text( '${isMicOriginalOn.value ? 'Disable' : 'Enable'} Original Sound', style: GoogleFonts.lato( textStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.normal, color: Colors.black, ), ), ), onTap: () async => { debugPrint("${isMicOriginalOn.value}"), await zoom.audioSettingHelper.enableMicOriginalInput(!isMicOriginalOn.value), isMicOriginalOn.value = await zoom.audioSettingHelper.isMicOriginalInputEnable(), debugPrint("Original sound ${isMicOriginalOn.value ? 'Enabled' : 'Disabled'}"), Navigator.of(context).pop(), }, ) ]; if (canCallOutToCRC) { options.add(ListTile( title: Text( 'Call-out to CRC devices', style: GoogleFonts.lato( textStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.normal, color: Colors.black, ), ), ), onTap: () async => { debugPrint('CRC result = ${await zoom.CRCHelper.callCRCDevice("bjn.vc", ZoomVideoSdkCRCProtocolType.SIP)}'), Navigator.of(context).pop(), }, )); options.add(ListTile( title: Text( 'Cancel call-out to CRC devices', style: GoogleFonts.lato( textStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.normal, color: Colors.black, ), ), ), onTap: () async => { debugPrint('cancel result= ${await zoom.CRCHelper.cancelCallCRCDevice()}'), Navigator.of(context).pop(), }, )); } if (canSwitchSpeaker) { options.add(ListTile( title: Text( 'Turn ${isSpeakerOn.value ? 'off' : 'on'} Speaker', style: GoogleFonts.lato( textStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.normal, color: Colors.black, ), ), ), onTap: () async => { await zoom.audioHelper.setSpeaker(!isSpeakerOn.value), isSpeakerOn.value = await zoom.audioHelper.getSpeakerStatus(), debugPrint('Turned ${isSpeakerOn.value ? 'on' : 'off'} Speaker'), Navigator.of(context).pop(), }, )); } if (isHost) { options.add(ListTile( title: Text( '${isShareLocked ? 'Unlock' : 'Lock'} Share', style: GoogleFonts.lato( textStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.normal, color: Colors.black, ), ), ), onTap: () async => { debugPrint("isShareLocked = ${await zoom.shareHelper.lockShare(!isShareLocked)}"), Navigator.of(context).pop(), })); options.add(ListTile( title: Text( 'Change Name', style: GoogleFonts.lato( textStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.normal, color: Colors.black, ), ), ), onTap: () => { isRenameModalVisible.value = true, Navigator.of(context).pop(), }, )); } if (canStartLiveTranscription) { options.add(ListTile( title: Text( "${startLiveTranscription ? 'Stop' : 'Start'} Live Transcription", style: GoogleFonts.lato( textStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.normal, color: Colors.black, ), ), ), onTap: () async => { if (startLiveTranscription) { debugPrint('stopLiveTranscription= ${await zoom.liveTranscriptionHelper.stopLiveTranscription()}'), } else { debugPrint('startLiveTranscription= ${await zoom.liveTranscriptionHelper.startLiveTranscription()}'), }, Navigator.of(context).pop(), }, )); options.add(ListTile( title: Text( '${isReceiveSpokenLanguageContentEnabled.value ? 'Disable' : 'Enable'} receiving original caption', style: GoogleFonts.lato( textStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.normal, color: Colors.black, ), ), ), onTap: () async => { await zoom.liveTranscriptionHelper.enableReceiveSpokenLanguageContent(!isReceiveSpokenLanguageContentEnabled.value), isReceiveSpokenLanguageContentEnabled.value = await zoom.liveTranscriptionHelper.isReceiveSpokenLanguageContentEnabled(), debugPrint("isReceiveSpokenLanguageContentEnabled = ${isReceiveSpokenLanguageContentEnabled.value}"), Navigator.of(context).pop(), })); } if (canStartRecording) { options.add(ListTile( title: Text( '${isRecordingStarted.value ? 'Stop' : 'Start'} Recording', style: GoogleFonts.lato( textStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.normal, color: Colors.black, ), ), ), onTap: () async => { if (!isRecordingStarted.value) { debugPrint('isRecordingStarted = ${await zoom.recordingHelper.startCloudRecording()}'), } else { debugPrint('isRecordingStarted = ${await zoom.recordingHelper.stopCloudRecording()}'), }, Navigator.of(context).pop(), })); } if (isVideoOn.value) { options.add(ListTile( title: Text( 'Mirror the video', style: GoogleFonts.lato( textStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.normal, color: Colors.black, ), ), ), onTap: () async => { await zoom.videoHelper.mirrorMyVideo(!isVideoMirrored.value), isVideoMirrored.value = await zoom.videoHelper.isMyVideoMirrored(), Navigator.of(context).pop(), })); options.add(ListTile( title: Text( '${isOriginalAspectRatio.value ? 'Enable' : 'Disable'} original aspect ratio', style: GoogleFonts.lato( textStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.normal, color: Colors.black, ), ), ), onTap: () async => { await zoom.videoHelper.enableOriginalAspectRatio(!isOriginalAspectRatio.value), isOriginalAspectRatio.value = await zoom.videoHelper.isOriginalAspectRatioEnabled(), debugPrint("isOriginalAspectRatio= ${isOriginalAspectRatio.value}"), Navigator.of(context).pop(), })); } showDialog( context: context, builder: (context) { return Dialog( elevation: 0.0, insetPadding: const EdgeInsets.symmetric(horizontal: 40), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), child: SizedBox( height: 500, child: Scrollbar( child: ListView( shrinkWrap: true, scrollDirection: Axis.vertical, children: ListTile.divideTiles( context: context, tiles: options, ).toList(), ), ), ), ); }); } void onLeaveSession(bool isEndSession) async { await zoom.leaveSession(isEndSession); Navigator.pop(context); // Navigator.pop(context); } void showLeaveOptions() async { ZoomVideoSdkUser? mySelf = await zoom.session.getMySelf(); bool isHost = await mySelf!.getIsHost(); onLeaveSession(true); // Widget endSession; // Widget leaveSession; // Widget cancel = TextButton( // child: const Text('Cancel'), // onPressed: () { // Navigator.pop(context); //close Dialog // }, // ); // switch (defaultTargetPlatform) { // case TargetPlatform.android: // endSession = TextButton( // child: const Text('End Session'), // onPressed: () => onLeaveSession(true), // ); // leaveSession = TextButton( // child: const Text('Leave Session'), // onPressed: () => onLeaveSession(false), // ); // break; // default: // endSession = CupertinoActionSheetAction( // isDestructiveAction: true, // child: const Text('End Session'), // onPressed: () => onLeaveSession(true), // ); // leaveSession = CupertinoActionSheetAction( // child: const Text('Leave Session'), // onPressed: () => onLeaveSession(false), // ); // break; // } // // List options = [ // leaveSession, // cancel, // ]; // // if (Platform.isAndroid) { // if (isHost) { // options.removeAt(1); // options.insert(0, endSession); // } // showDialog( // context: context, // builder: (context) { // return AlertDialog( // content: const Text("Do you want to leave this session?"), // shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(2.0))), // actions: options, // ); // }); // } else { // options.removeAt(1); // if (isHost) { // options.insert(1, endSession); // } // showCupertinoModalPopup( // context: context, // builder: (context) => CupertinoActionSheet( // message: const Text('Are you sure that you want to leave the call?'), // actions: options, // cancelButton: cancel, // ), // ); // } } final chatMessageController = TextEditingController(); void sendChatMessage(String message) async { await zoom.chatHelper.sendChatToAll(message); ZoomVideoSdkUser? self = await zoom.session.getMySelf(); ZoomVideoSdkCmdChannel cmdChannel = zoom.cmdChannel; for (var user in users.value) { if (user.userId != self?.userId) { await zoom.cmdChannel.sendCommand(user.userId, message); } } chatMessageController.clear(); // send the chat as a command } void onSelectedUser(ZoomVideoSdkUser user) async { setState(() { fullScreenUser.value = user; }); } Widget fullScreenView; Widget smallView; if (isInSession.value && fullScreenUser.value != null && users.value.isNotEmpty) { fullScreenView = AnimatedOpacity( opacity: opacityLevel, duration: const Duration(seconds: 3), child: VideoView( user: fullScreenUser.value, hasMultiCamera: false, sharing: sharingUser.value == null ? false : (sharingUser.value?.userId == fullScreenUser.value?.userId), preview: false, focused: false, multiCameraIndex: "0", videoAspect: VideoAspect.Original, fullScreen: true, resolution: VideoResolution.Resolution360, isPiPView: false, ), ); smallView = Container( height: 110, margin: const EdgeInsets.only(left: 20, right: 20), alignment: Alignment.bottomLeft, child: VideoView( user: users.value[0], hasMultiCamera: false, sharing: sharingUser.value == null ? false : sharingUser.value?.userId == users.value[0].userId, preview: false, focused: false, multiCameraIndex: "0", videoAspect: VideoAspect.Original, fullScreen: false, resolution: VideoResolution.Resolution180, isPiPView: false, ), // ListView.separated( // scrollDirection: Axis.horizontal, // itemCount: users.value.length, // itemBuilder: (BuildContext context, int index) { // return InkWell( // onTap: () async { // onSelectedUser(users.value[index]); // }, // onDoubleTap: () async { // onSelectedUserVolume(users.value[index]); // }, // child: Center( // child: VideoView( // user: users.value[index], // hasMultiCamera: false, // sharing: sharingUser.value == null ? false : sharingUser.value?.userId == users.value[index].userId, // preview: false, // focused: false, // multiCameraIndex: "0", // videoAspect: VideoAspect.Original, // fullScreen: false, // resolution: VideoResolution.Resolution180, // isPiPView: false, // ), // ), // ); // }, // separatorBuilder: (BuildContext context, int index) => const Divider(), // ), ); } else { fullScreenView = Container( color: Colors.black, child: const Center( child: Text( "Connecting...", style: TextStyle( fontSize: 20, color: Colors.white, ), ), )); smallView = Container( height: 110, color: Colors.transparent, ); } _changeOpacity; return Scaffold( resizeToAvoidBottomInset: false, backgroundColor: backgroundColor, body: Stack( children: [ fullScreenView, Container( padding: const EdgeInsets.only(top: 35), child: Stack( children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // Container( // height: 70, // width: 150, // margin: const EdgeInsets.only(top: 16, left: 8), // padding: const EdgeInsets.all(8), // alignment: Alignment.topLeft, // decoration: BoxDecoration( // borderRadius: const BorderRadius.all(Radius.circular(8.0)), // color: buttonBackgroundColor, // ), // child: InkWell( // onTap: () async { // showDialog( // context: context, // builder: (context) { // return Dialog( // elevation: 0.0, // insetPadding: const EdgeInsets.symmetric(horizontal: 40), // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), // child: SizedBox( // height: 280, // width: 200, // child: Column( // crossAxisAlignment: CrossAxisAlignment.stretch, // children: [ // ListView( // shrinkWrap: true, // children: ListTile.divideTiles( // context: context, // tiles: [ // ListTile( // title: Text( // 'Session Information', // style: GoogleFonts.lato( // textStyle: const TextStyle( // fontSize: 18, // fontWeight: FontWeight.w600, // ), // ), // ), // ), // ListTile( // title: Text( // 'Session Name', // style: GoogleFonts.lato( // textStyle: const TextStyle( // fontSize: 14, // ), // ), // ), // subtitle: Text( // sessionName.value, // style: GoogleFonts.lato( // textStyle: const TextStyle( // fontSize: 12, // ), // ), // ), // ), // ListTile( // title: Text( // 'Session Password', // style: GoogleFonts.lato( // textStyle: const TextStyle( // fontSize: 14, // ), // ), // ), // subtitle: Text( // sessionPassword.value, // style: GoogleFonts.lato( // textStyle: const TextStyle( // fontSize: 12, // ), // ), // ), // ), // ListTile( // title: Text( // 'Participants', // style: GoogleFonts.lato( // textStyle: const TextStyle( // fontSize: 14, // ), // ), // ), // subtitle: Text( // '${users.value.length}', // style: GoogleFonts.lato( // textStyle: const TextStyle( // fontSize: 12, // ), // ), // ), // ), // ], // ).toList(), // ), // ], // ), // )); // }); // }, // child: Stack( // children: [ // // Column( // // children: [ // // const Padding(padding: EdgeInsets.symmetric(vertical: 3)), // // Align( // // alignment: Alignment.centerLeft, // // child: Text( // // sessionName.value, // // overflow: TextOverflow.ellipsis, // // style: GoogleFonts.lato( // // textStyle: const TextStyle( // // fontSize: 14, // // fontWeight: FontWeight.w600, // // color: Colors.white, // // ), // // ), // // ), // // ), // // const Padding(padding: EdgeInsets.symmetric(vertical: 5)), // // Align( // // alignment: Alignment.centerLeft, // // child: Text( // // "Participants: ${users.value.length}", // // style: GoogleFonts.lato( // // textStyle: const TextStyle( // // fontSize: 14, // // fontWeight: FontWeight.w600, // // color: Colors.white, // // ), // // ), // // ), // // ) // // ], // // ), // // Container( // // alignment: Alignment.centerRight, // // child: Image.asset( // // "assets/icons/unlocked@2x.png", // // height: 22, // // )), // ], // ), // ), // ), TextButton( onPressed: (showLeaveOptions), child: Container( alignment: Alignment.topRight, margin: const EdgeInsets.only(top: 16, right: 8), padding: const EdgeInsets.only(top: 5, bottom: 5, left: 16, right: 16), height: 28, decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(20.0)), color: buttonBackgroundColor, ), child: const Text( "LEAVE", style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: Color(0xFFE02828), ), ), )), ], ), Align( alignment: Alignment.centerRight, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( onPressed: onPressAudio, icon: isMuted.value ? Image.asset("assets/images/new/zoom/unmute@2x.png") : Image.asset("assets/images/new/zoom/mute@2x.png"), iconSize: circleButtonSize, tooltip: isMuted.value == true ? "Unmute" : "Mute", ), // IconButton( // onPressed: onPressShare, // icon: isSharing.value ? Image.asset("assets/images/new/zoom/share-off@2x.png") : Image.asset("assets/images/new/zoom/share-on@2x.png"), // iconSize: circleButtonSize, // ), IconButton( onPressed: onPressVideo, iconSize: circleButtonSize, icon: isVideoOn.value ? Image.asset("assets/images/new/zoom/video-off@2x.png") : Image.asset("assets/images/new/zoom/video-on@2x.png"), ), IconButton( onPressed: onPressCameraChange, iconSize: circleButtonSize, icon: const Icon( Icons.cameraswitch_sharp, size: 45.0, color: Colors.white, ), ), // IconButton( // onPressed: onPressMore, // icon: Image.asset("assets/icons/more@2x.png"), // iconSize: circleButtonSize, // ), ], ), ), Container( alignment: Alignment.bottomLeft, margin: const EdgeInsets.only(bottom: 120), child: smallView, ), // CommentList(zoom: zoom, eventListener: eventListener), moreOptions, ], )), ], ) // drawer: const MenuBar() ); } } class CallArguments { final bool isJoin; final String sessionName; final String sessionPwd; final String displayName; final String sessionIdleTimeoutMins; final String role; CallArguments(this.sessionName, this.sessionPwd, this.displayName, this.sessionIdleTimeoutMins, this.role, this.isJoin); } class JoinArguments { final bool isJoin; final String sessionName; final String sessionPwd; final String displayName; final String sessionTimeout; final String roleType; JoinArguments(this.isJoin, this.sessionName, this.sessionPwd, this.displayName, this.sessionTimeout, this.roleType); }