From 11ec24c77e93ef6e627afb71098adbd45eb908a1 Mon Sep 17 00:00:00 2001 From: Sultan khan <> Date: Thu, 18 Jul 2024 11:29:24 +0300 Subject: [PATCH] zoom sdk integrated --- android/app/build.gradle | 12 +- android/build.gradle | 2 +- lib/config/config.dart | 10 +- .../patient/LiveCarePatientServices.dart | 20 +- lib/main.dart | 1 + lib/routes.dart | 5 + lib/screens/home/home_screen.dart | 33 + lib/screens/video_call_zoom/config.dart | 4 + lib/screens/video_call_zoom/jwt.dart | 40 + lib/screens/video_call_zoom/video_view.dart | 258 +++ .../video_call_zoom/zoom_video_call.dart | 1448 +++++++++++++++++ pubspec.yaml | 6 + 12 files changed, 1820 insertions(+), 19 deletions(-) create mode 100644 lib/screens/video_call_zoom/config.dart create mode 100644 lib/screens/video_call_zoom/jwt.dart create mode 100644 lib/screens/video_call_zoom/video_view.dart create mode 100644 lib/screens/video_call_zoom/zoom_video_call.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 5457931e..a4100f54 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 33 + compileSdkVersion 34 signingConfigs { release { @@ -48,8 +48,8 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.hmg.hmgDr" - minSdkVersion 21 - targetSdkVersion 33 + minSdkVersion 24 + targetSdkVersion 34 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -100,6 +100,12 @@ dependencies { androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' + //ZOOM + implementation "us.zoom.videosdk:zoomvideosdk-core:1.10.1" + implementation "us.zoom.videosdk:zoomvideosdk-annotation:1.10.1" + implementation "us.zoom.videosdk:zoomvideosdk-videoeffects:1.10.1" + + //openTok implementation 'com.opentok.android:opentok-android-sdk:2.20.1' //permissions diff --git a/android/build.gradle b/android/build.gradle index 5254f6aa..c6a0e369 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.0.3' + classpath 'com.android.tools.build:gradle:7.1.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.google.gms:google-services:4.3.3' } diff --git a/lib/config/config.dart b/lib/config/config.dart index e7423937..c2828a62 100644 --- a/lib/config/config.dart +++ b/lib/config/config.dart @@ -4,14 +4,14 @@ const MAX_SMALL_SCREEN = 660; const ONLY_NUMBERS = "[0-9]"; const ONLY_LETTERS = "[a-zA-Z &'\"]"; const ONLY_DATE = "[0-9/]"; -const BASE_URL_LIVE_CARE = 'https://livecare.hmg.com/'; +// const BASE_URL_LIVE_CARE = 'https://livecare.hmg.com/'; const DOCTOR_ROTATION = 'https://doctorrota.hmg.com/'; -// const BASE_URL_LIVE_CARE = 'https://livecareuat.hmg.com/'; -// const BASE_URL = 'https://hmgwebservices.com/'; - +const BASE_URL_LIVE_CARE = 'https://livecareuat.hmg.com/'; +const BASE_URL = 'https://hmgwebservices.com/'; +// const BASE_URL = 'http://10.20.200.111:1010/'; // const BASE_URL = 'https://uat.hmgwebservices.com/'; -const BASE_URL = 'https://webservices.hmg.com/'; +// const BASE_URL = 'https://webservices.hmg.com/'; // const BASE_URL = 'https://vidauat.cloudsolutions.com.sa/'; //Vida Plus URL diff --git a/lib/core/service/patient/LiveCarePatientServices.dart b/lib/core/service/patient/LiveCarePatientServices.dart index 9a3079f3..4c9d0beb 100644 --- a/lib/core/service/patient/LiveCarePatientServices.dart +++ b/lib/core/service/patient/LiveCarePatientServices.dart @@ -53,16 +53,16 @@ class LiveCarePatientServices extends BaseService { }); /// remove items. - List removedPatientList = []; - _patientList.forEach((element) { - if (localPatientList.any((it) => it.patientId == element.patientId)) { - removedPatientList.add(element); - } - }); - - removedPatientList.forEach((element) { - _patientList.remove(element); - }); + // List removedPatientList = []; + // _patientList.forEach((element) { + // if (localPatientList.any((it) => it.patientId == element.patientId)) { + // removedPatientList.add(element); + // } + // }); + + // removedPatientList.forEach((element) { + // _patientList.remove(element); + // }); }, onFailure: (String error, int statusCode) { hasError = true; diff --git a/lib/main.dart b/lib/main.dart index 92478404..a4aebd08 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -30,6 +30,7 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { AppGlobal.CONTEX = context; + return LayoutBuilder( builder: (context, constraints) { return OrientationBuilder(builder: (context, orientation) { diff --git a/lib/routes.dart b/lib/routes.dart index 71434262..0dbe8653 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -30,6 +30,7 @@ import 'package:doctor_app_flutter/screens/patients/profile/vte_assessment/vte_a import 'package:doctor_app_flutter/screens/prescription/new_prescriptions_page.dart'; import 'package:doctor_app_flutter/screens/prescription/old_prescriptions_page.dart'; import 'package:doctor_app_flutter/screens/procedures/procedure_screen.dart'; +import 'package:doctor_app_flutter/screens/video_call_zoom/zoom_video_call.dart'; import './screens/auth/login_screen.dart'; import './screens/patients/profile/vital_sign/vital_sign_details_screen.dart'; import 'landing_page.dart'; @@ -87,6 +88,8 @@ const String DISCHARGE_SUMMARY = 'discharge_summary'; const String PHARMACY_INTERVENTION = 'new_medication'; const String INTERVENTION_MEDICATION = 'intervention_medication'; +const String ZOOM_CALL_PAGE ='zoom-call'; + //todo: change the routing way. var routes = { ROOT: (_) => RootPage(), @@ -139,4 +142,6 @@ var routes = { DIABETIC_CHART_VALUES: (_) => DiabeticChart(), PHARMACY_INTERVENTION: (_) => PharmacyInterventionScreen(), VTE_ASSESSMENT: (_) => VteAssessmentScreen(), + ZOOM_CALL_PAGE: (_) => CallScreen(), + }; diff --git a/lib/screens/home/home_screen.dart b/lib/screens/home/home_screen.dart index 491efdd1..15fe8db9 100644 --- a/lib/screens/home/home_screen.dart +++ b/lib/screens/home/home_screen.dart @@ -30,9 +30,12 @@ import 'package:doctor_app_flutter/widgets/shared/loader/gif_loader_dialog_utils import 'package:doctor_app_flutter/widgets/transitions/fade_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_zoom_videosdk/native/zoom_videosdk.dart'; import 'package:provider/provider.dart'; import 'package:sticky_headers/sticky_headers/widget.dart'; +import '../../utils/notification_permission_utils.dart'; +import '../video_call_zoom/zoom_video_call.dart'; import 'label.dart'; class HomeScreen extends StatefulWidget { @@ -76,6 +79,8 @@ class _HomeScreenState extends State { print("onModelReady radiologyCriticalFindingModel EMPTYYYY!!!"); } }); + startZoom(); + }, builder: (_, model, w) => AppScaffold( baseViewModel: model, @@ -98,7 +103,26 @@ class _HomeScreenState extends State { onPressed: () => Scaffold.of(context).openDrawer(), ), ), + Column(children: [ + + + + InkWell(onTap: (){ + AppPermissionsUtils.requestVideoCallPermission( + context: context, + onTapGrant: () { + Navigator.pushNamed( + context, + "zoom-call", + arguments: CallArguments("ZoomTestHMG", "123", "Doctor", "40", "0", true), + ); + + }); + + }, + child: Text("Zoom Call"), + ), ProfileWelcomeWidget( Row( mainAxisAlignment: MainAxisAlignment.start, @@ -483,4 +507,13 @@ class _HomeScreenState extends State { colorIndex = 0; } } + void startZoom(){ + var zoom = ZoomVideoSdk(); + InitConfig initConfig = InitConfig( + domain: "zoom.us", + enableLog: true, + ); + zoom.initSdk(initConfig); + } + } diff --git a/lib/screens/video_call_zoom/config.dart b/lib/screens/video_call_zoom/config.dart new file mode 100644 index 00000000..09370c00 --- /dev/null +++ b/lib/screens/video_call_zoom/config.dart @@ -0,0 +1,4 @@ +const Map configs = { + 'ZOOM_SDK_KEY': 'b9T74nhfTg-ioP9urm970A', + 'ZOOM_SDK_SECRET': 'KOzmjBNXQ1f4IPHpnngfL29uZvJMufSy2Fk8', +}; diff --git a/lib/screens/video_call_zoom/jwt.dart b/lib/screens/video_call_zoom/jwt.dart new file mode 100644 index 00000000..980c4c66 --- /dev/null +++ b/lib/screens/video_call_zoom/jwt.dart @@ -0,0 +1,40 @@ +import 'dart:math'; + +import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart'; + +import 'config.dart'; + +String makeId(int length) { + String result = ""; + String characters = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + int charactersLength = characters.length; + for (var i = 0; i < length; i++) { + result += characters[Random().nextInt(charactersLength)]; + } + return result; +} + +String generateJwt(String sessionName, String roleType) { + try { + var iat = DateTime.now(); + var exp = DateTime.now().add(Duration(days: 2)); + final jwt = JWT( + { + 'app_key': configs["ZOOM_SDK_KEY"], + 'version': 1, + 'user_identity': makeId(10), + 'iat': (iat.millisecondsSinceEpoch / 1000).round(), + 'exp': (exp.millisecondsSinceEpoch / 1000).round(), + 'tpc': sessionName, + 'role_type': int.parse(roleType), + 'cloud_recording_option': 1, + }, + ); + var token = jwt.sign(SecretKey(configs["ZOOM_SDK_SECRET"])); + return token; + } catch (e) { + print(e); + return ''; + } +} diff --git a/lib/screens/video_call_zoom/video_view.dart b/lib/screens/video_call_zoom/video_view.dart new file mode 100644 index 00000000..f549be26 --- /dev/null +++ b/lib/screens/video_call_zoom/video_view.dart @@ -0,0 +1,258 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_zoom_videosdk/flutter_zoom_view.dart' as FlutterZoomView; +import 'package:flutter_zoom_videosdk/native/zoom_videosdk.dart'; + +const SHOW_TALKING_ICON_DURATION = 2000; + +class VideoView extends FlutterZoomView.ZoomView { + const VideoView({ + super.key, + required super.user, + required super.sharing, + required super.preview, + required super.focused, + required super.hasMultiCamera, + required super.multiCameraIndex, + required super.videoAspect, + required super.fullScreen, + required super.resolution, + required super.isPiPView, + }); + + @override + Widget build(BuildContext context) { + var isVideoOn = useState(false); + var isTalking = useState(false); + var isMuted = useState(false); + var isMounted = useIsMounted(); + var zoom = ZoomVideoSdk(); + var isSharing = useState(false); + user?.audioStatus?.isMuted().then((muted) => isMuted.value = muted); + + useEffect(() { + updateVideoStatus() { + if (user == null) return; + Future.microtask(() async { + if (isMounted()) { + isVideoOn.value = (await user!.videoStatus!.isOn()); + isSharing.value = sharing; + } + }); + } + + resetAudioStatus() { + isTalking.value = false; + isMuted.value = false; + } + + updateAudioStatus() async { + if (!isMounted()) return; + var talking = await user?.audioStatus?.isTalking(); + var muted = await user?.audioStatus?.isMuted(); + isMuted.value = muted!; + isTalking.value = talking!; + if (talking) { + Timer( + const Duration(milliseconds: SHOW_TALKING_ICON_DURATION), + () => { + if (isMounted()) + { + isTalking.value = false, + } + }); + } + } + + updateVideoStatus(); + return null; + }, [zoom, user]); + + ImageIcon audioStatusIcon; + if (isTalking.value) { + audioStatusIcon = const ImageIcon( + AssetImage("assets/imgaes/zoom/talking@2x.png"), + ); + } else if (isMuted.value) { + audioStatusIcon = const ImageIcon( + AssetImage("assets/imgaes/zoom/muted@2x.png"), + ); + } + // Pass parameters to the platform side. + final Map creationParams = {}; + creationParams.putIfAbsent("userId", () => user?.userId); + creationParams.putIfAbsent("sharing", () => sharing); + creationParams.putIfAbsent("preview", () => preview); + creationParams.putIfAbsent("focused", () => focused); + creationParams.putIfAbsent("hasMultiCamera", () => hasMultiCamera); + if (videoAspect.isEmpty) { + creationParams.putIfAbsent("videoAspect", () => VideoAspect.PanAndScan); + } else { + creationParams.putIfAbsent("videoAspect", () => videoAspect); + } + creationParams.putIfAbsent("fullScreen", () => fullScreen); + if (resolution.isNotEmpty) { + creationParams.putIfAbsent("videoAspect", () => videoAspect); + } + + if (fullScreen) { + if (sharing) { + return Container( + height: MediaQuery.of(context).size.height, + width: MediaQuery.of(context).size.width, + alignment: Alignment.center, + child: FlutterZoomView.View(creationParams: creationParams), + ); + } else if (isVideoOn.value) { + return Container( + height: MediaQuery.of(context).size.height, + width: MediaQuery.of(context).size.width, + alignment: Alignment.center, + child: FlutterZoomView.View(creationParams: creationParams), + ); + } else { + return Container( + margin: const EdgeInsets.symmetric(vertical: 0), + child: Container( + alignment: Alignment.center, + child: const Image( + image: AssetImage("assets/imgaes/zoom/default-avatar.png"), + )), + ); + } + } else { + if (isVideoOn.value || sharing) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8), + height: 110, + width: 110, + child: Stack( + children: [ + Container( + height: 110, + width: 110, + decoration: BoxDecoration( + color: const Color(0xff232323), + border: Border.all( + color: const Color(0xff666666), + width: 1, + ), + borderRadius: const BorderRadius.all(Radius.circular(8)), + ), + alignment: Alignment.center, + child: FlutterZoomView.View(creationParams: creationParams), + ), + Container( + height: 110, + width: 110, + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + ), + Container( + alignment: Alignment.bottomCenter, + child: Container( + height: 20, + width: 110, + decoration: const BoxDecoration( + color: Colors.black26, + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + ), + ), + Container( + alignment: Alignment.bottomLeft, + margin: const EdgeInsets.only(left: 5), + child: Text( + user!.userName, + textAlign: TextAlign.left, + style: const TextStyle( + color: Colors.white, + fontSize: 12, + ), + ), + ), + Container( + alignment: Alignment.bottomRight, + margin: const EdgeInsets.only(right: 5, bottom: 5), + // height: 110, + // width: 110, + child: Image( + height: 12, + width: 12, + image: isMuted.value ? const AssetImage("assets/imgaes/zoom/muted@2x.png") : const AssetImage("assets/imgaes/zoom/talking@2x.png"), + fit: BoxFit.cover, + ), + ) + ], + ), + ); + } else { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8), + height: 110, + width: 110, + child: Stack( + children: [ + Container( + height: 110, + width: 110, + decoration: BoxDecoration( + color: const Color(0xff232323), + border: Border.all( + color: const Color(0xff666666), + width: 1, + ), + borderRadius: const BorderRadius.all(Radius.circular(8)), + ), + alignment: Alignment.center, + child: Container( + alignment: Alignment.center, + child: const Image( + height: 60, + width: 60, + image: AssetImage("assets/imgaes/zoom/default-avatar.png"), + )), + ), + Align( + alignment: Alignment.bottomCenter, + child: Container( + height: 20, + width: 110, + decoration: const BoxDecoration( + color: Colors.black26, + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + ), + ), + Container( + alignment: Alignment.bottomLeft, + margin: const EdgeInsets.only(left: 5), + child: Text( + user!.userName, + textAlign: TextAlign.left, + style: const TextStyle( + color: Colors.white, + fontSize: 12, + ), + ), + ), + Container( + alignment: Alignment.bottomRight, + margin: const EdgeInsets.only(right: 5, bottom: 5), + child: Image( + height: 12, + width: 12, + image: isMuted.value ? const AssetImage("assets/imgaes/zoom/muted@2x.png") : const AssetImage("assets/imgaes/zoom/talking@2x.png"), + fit: BoxFit.cover, + ), + ), + ], + ), + ); + } + } + } +} diff --git a/lib/screens/video_call_zoom/zoom_video_call.dart b/lib/screens/video_call_zoom/zoom_video_call.dart new file mode 100644 index 00000000..fe46d8d0 --- /dev/null +++ b/lib/screens/video_call_zoom/zoom_video_call.dart @@ -0,0 +1,1448 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'package:doctor_app_flutter/screens/video_call_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 'jwt.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; + 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; + }); + + 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; + }); + + 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!) { + 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', + ), + ), + 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', + ), + 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', + + ), + 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', + + + ), + ), + ListTile( + title: Text( + 'Current volume', + + ), + 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', + + ), + 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', + + ), + 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', + + ), + ), + ListTile( + title: Text( + 'is Support Virtual Background', + + ), + onTap: () async => { + debugPrint("${await zoom.virtualBackgroundHelper.isSupportVirtualBackground()}"), + Navigator.of(context).pop(), + }, + ), + ListTile( + title: Text( + 'Get Chat Privilege', + + ), + onTap: () async => { + debugPrint("Chat Privilege = ${await zoom.chatHelper.getChatPrivilege()}"), + Navigator.of(context).pop(), + }, + ), + ListTile( + title: Text( + 'Get Session Dial-in Number infos', + + + ), + onTap: () async => { + debugPrint("session number = ${await zoom.session.getSessionNumber()}"), + Navigator.of(context).pop(), + }, + ), + ListTile( + title: Text( + '${isMicOriginalOn.value ? 'Disable' : 'Enable'} Original Sound', + + ), + 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', + + ), + 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', + + ), + 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', + + ), + 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', + + ), + onTap: () async => { + debugPrint("isShareLocked = ${await zoom.shareHelper.lockShare(!isShareLocked)}"), + Navigator.of(context).pop(), + })); + options.add(ListTile( + title: Text( + 'Change Name', + + ), + onTap: () => { + isRenameModalVisible.value = true, + Navigator.of(context).pop(), + }, + )); + } + + if (canStartLiveTranscription) { + options.add(ListTile( + title: Text( + "${startLiveTranscription ? 'Stop' : 'Start'} Live Transcription", + + ), + 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', + + ), + 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', + + ), + 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', + + ), + 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', + + ), + 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 { + + Navigator.pop(context); + Navigator.pop(context); + await zoom.leaveSession(isEndSession); + // if (context.mounted) { + // await Navigator.popAndPushNamed( + // context, + // "Join", + // arguments: JoinArguments(!isEndSession, sessionName.value, sessionPassword.value, args.displayName, args.sessionIdleTimeoutMins, args.role), + // ); + // } + } + + void showLeaveOptions() async { + ZoomVideoSdkUser? mySelf = await zoom.session.getMySelf(); + bool isHost = await mySelf!.getIsHost(); + + 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(true), + ); + break; + default: + endSession = CupertinoActionSheetAction( + isDestructiveAction: true, + child: const Text('End Session'), + onPressed: () => onLeaveSession(true), + ); + leaveSession = CupertinoActionSheetAction( + child: const Text('Leave Session'), + onPressed: () => onLeaveSession(true), + ); + 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 session?'), + 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.center, + child: 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', + + ), + ), + ListTile( + title: Text( + 'Session Name', + + ), + subtitle: Text( + sessionName.value, + + ), + ), + ListTile( + title: Text( + 'Session Password', + + ), + subtitle: Text( + sessionPassword.value, + + ), + ), + ListTile( + title: Text( + 'Participants', + + ), + subtitle: Text( + '${users.value.length}', + + ), + ), + ], + ).toList(), + ), + ], + ), + )); + }); + }, + child: Stack( + children: [ + Column( + children: [ + const Padding(padding: EdgeInsets.symmetric(vertical: 3)), + Align( + alignment: Alignment.centerLeft, + child: Text( + sessionName.value, + overflow: TextOverflow.ellipsis, + + ), + ), + const Padding(padding: EdgeInsets.symmetric(vertical: 5)), + Align( + alignment: Alignment.centerLeft, + child: Text( + "Participants: ${users.value.length}", + + ), + ) + ], + ), + Container( + alignment: Alignment.centerRight, + child: Image.asset( + "assets/images/zoom/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/zoom/unmute@2x.png") : Image.asset("assets/images/zoom/mute@2x.png"), + iconSize: circleButtonSize, + tooltip: isMuted.value == true ? "Unmute" : "Mute", + ), + IconButton( + onPressed: onPressShare, + icon: isSharing.value ? Image.asset("assets/images/zoom/share-off@2x.png") : Image.asset("assets/images/zoom/share-on@2x.png"), + iconSize: circleButtonSize, + ), + IconButton( + onPressed: onPressVideo, + iconSize: circleButtonSize, + icon: isVideoOn.value ? Image.asset("assets/images/zoom/video-off@2x.png") : Image.asset("assets/images/zoom/video-on@2x.png"), + ), + IconButton( + onPressed: onPressCameraChange, + iconSize: circleButtonSize, + icon: const Icon(Icons.cameraswitch_sharp, size: 65.0), + ), + IconButton( + onPressed: onPressMore, + icon: Image.asset("assets/images/zoom/more@2x.png"), + iconSize: circleButtonSize, + ), + ], + ), + ), + // Container( + // margin: const EdgeInsets.only(left: 16, right: 16, bottom: 40, top: 10), + // alignment: Alignment.bottomCenter, + // child: SizedBox( + // height: MediaQuery.of(context).viewInsets.bottom == 0 ? 65 : MediaQuery.of(context).viewInsets.bottom + 18, + // child: TextField( + // maxLines: 1, + // textAlign: TextAlign.left, + // style: TextStyle(color: chatTextColor), + // cursorColor: chatTextColor, + // textAlignVertical: TextAlignVertical.center, + // controller: chatMessageController, + // decoration: InputDecoration( + // contentPadding: const EdgeInsets.only(left: 16, top: 10, bottom: 10, right: 16), + // enabledBorder: OutlineInputBorder( + // borderSide: BorderSide(width: 1, color: chatTextColor), //<-- SEE HERE + // ), + // hintText: 'Type comment', + // hintStyle: TextStyle( + // fontSize: 14.0, + // color: chatTextColor, + // ), + // ), + // onSubmitted: (String str) { + // sendChatMessage(str); + // }, + // ), + // ), + // ), + 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); +} diff --git a/pubspec.yaml b/pubspec.yaml index 086f8a89..d7fdc516 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -130,6 +130,10 @@ dependencies: flutter_math_fork: ^0.7.2 + #zoom video plugin + + flutter_zoom_videosdk: ^1.10.11 + dart_jsonwebtoken: ^2.14.0 dependency_overrides: @@ -167,6 +171,8 @@ flutter: - assets/images/svgs/bottom_nav/ - assets/images/patient/ - assets/images/patient/vital_signs/ + - assets/images/zoom/ + # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware.