diff --git a/android/app/build.gradle b/android/app/build.gradle index 37d3d2d9..094cfd0c 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -48,7 +48,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.hmg.hmgDr" - minSdkVersion 21 + minSdkVersion 24 targetSdkVersion 34 versionCode flutterVersionCode.toInteger() versionName flutterVersionName @@ -79,6 +79,17 @@ android { // productionDebugImplementation // productionReleaseImplementation // } + + + packagingOptions { + exclude 'META-INF/proguard/androidx-annotations.pro' + pickFirst 'lib/x86/libc++_shared.so' + pickFirst 'lib/x86_64/libc++_shared.so' + pickFirst 'lib/armeabi-v7a/libc++_shared.so' + pickFirst 'lib/arm64-v8a/libc++_shared.so' + pickFirst '**/*.so' + } + compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 @@ -100,6 +111,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/lib/config/config.dart b/lib/config/config.dart index 98984cbc..11819234 100644 --- a/lib/config/config.dart +++ b/lib/config/config.dart @@ -4,10 +4,15 @@ 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://uat.hmgwebservices.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://hmgwebservices.com/'; // const BASE_URL = 'https://uat.hmgwebservices.com/'; diff --git a/lib/core/service/patient/LiveCarePatientServices.dart b/lib/core/service/patient/LiveCarePatientServices.dart index 2ee18a16..e9c40207 100644 --- a/lib/core/service/patient/LiveCarePatientServices.dart +++ b/lib/core/service/patient/LiveCarePatientServices.dart @@ -40,7 +40,7 @@ class LiveCarePatientServices extends BaseService { GET_PENDING_PATIENT_ER_FOR_DOCTOR_APP, onSuccess: (dynamic response, int statusCode) { List localPatientList = []; - + _patientList =[]; response['List_PendingPatientList'].forEach((v) { localPatientList.add(PatiantInformtion.fromJson(v)); }); @@ -53,12 +53,12 @@ class LiveCarePatientServices extends BaseService { }); /// remove items. - List removedPatientList = []; - _patientList.forEach((element) { - if (localPatientList.any((it) => it.patientId == element.patientId)) { - removedPatientList.add(element); - } - }); + // List removedPatientList = []; + // _patientList.forEach((element) { + // if (localPatientList.any((it) => it.patientId == element.patientId)) { + // removedPatientList.add(element); + // } + // }); // removedPatientList.forEach((element) { // _patientList.remove(element); diff --git a/lib/core/viewModel/LiveCarePatientViewModel.dart b/lib/core/viewModel/LiveCarePatientViewModel.dart index 84326d42..60e23833 100644 --- a/lib/core/viewModel/LiveCarePatientViewModel.dart +++ b/lib/core/viewModel/LiveCarePatientViewModel.dart @@ -269,4 +269,7 @@ class LiveCarePatientViewModel extends BaseViewModel { setState(ViewState.Idle); } } + + + } diff --git a/lib/main.dart b/lib/main.dart index 35398c97..24ab7384 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 fa0aa3ab..25dcf2a2 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -32,6 +32,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'; @@ -90,6 +91,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(), @@ -145,4 +148,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 857e48e8..d2575a9f 100644 --- a/lib/screens/home/home_screen.dart +++ b/lib/screens/home/home_screen.dart @@ -32,9 +32,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 '../patients/profile/soap_update/shared_soap_widgets/bottom_sheet_title.dart'; import 'label.dart'; @@ -93,6 +96,7 @@ class _HomeScreenState extends State { } } }); + startZoom(); }); }, builder: (_, model, w) => AppScaffold( @@ -120,7 +124,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, @@ -618,4 +641,12 @@ class _HomeScreenState extends State { }, ); } + void startZoom(){ + var zoom = ZoomVideoSdk(); + InitConfig initConfig = InitConfig( + domain: "zoom.us", + enableLog: true, + ); + zoom.initSdk(initConfig); + } } diff --git a/lib/screens/patients/profile/diagnosis/diagnosis_screen.dart b/lib/screens/patients/profile/diagnosis/diagnosis_screen.dart index f7b89bb4..eb994ff4 100644 --- a/lib/screens/patients/profile/diagnosis/diagnosis_screen.dart +++ b/lib/screens/patients/profile/diagnosis/diagnosis_screen.dart @@ -34,6 +34,9 @@ class _ProgressNoteState extends State { {bool isLocalBusy = false}) async { final routeArgs = ModalRoute.of(context)!.settings.arguments as Map; PatiantInformtion patient = routeArgs['patient']; + // String type = await sharedPref.getString(SLECTED_PATIENT_TYPE); + // + // print(type); String type = await sharedPref.getString(SLECTED_PATIENT_TYPE) == '0' ? "2" : "1"; print(type); diff --git a/lib/screens/patients/profile/profile_screen/patient_profile_screen.dart b/lib/screens/patients/profile/profile_screen/patient_profile_screen.dart index 588a9b13..2725ec19 100644 --- a/lib/screens/patients/profile/profile_screen/patient_profile_screen.dart +++ b/lib/screens/patients/profile/profile_screen/patient_profile_screen.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:doctor_app_flutter/config/config.dart'; import 'package:doctor_app_flutter/config/size_config.dart'; import 'package:doctor_app_flutter/core/enum/view_state.dart'; +import 'package:doctor_app_flutter/core/model/PatientRegistration/GetPatientInfoRequestModel.dart'; import 'package:doctor_app_flutter/core/model/SOAP/in_patient/post_episode_for_Inpatient_request_model.dart'; import 'package:doctor_app_flutter/core/model/SOAP/post_episode_req_model.dart'; import 'package:doctor_app_flutter/core/model/patient/patiant_info_model.dart'; @@ -21,8 +22,11 @@ import 'package:doctor_app_flutter/utils/translations_delegate_base_utils.dart'; import 'package:doctor_app_flutter/utils/utils.dart'; import 'package:doctor_app_flutter/widgets/patients/profile/app_bar/patient-profile-header-new-design-app-bar.dart'; import 'package:doctor_app_flutter/widgets/shared/app_scaffold_widget.dart'; +import 'package:doctor_app_flutter/widgets/shared/app_texts_widget.dart'; import 'package:doctor_app_flutter/widgets/shared/buttons/app_buttons_widget.dart'; import 'package:doctor_app_flutter/widgets/shared/loader/gif_loader_dialog_utils.dart'; +import 'package:doctor_app_flutter/widgets/shared/text_fields/app-textfield-custom.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:hexcolor/hexcolor.dart'; @@ -31,6 +35,7 @@ import 'package:quiver/async.dart'; import '../../../../locator.dart'; import '../../../../routes.dart'; +import '../../../video_call_zoom/zoom_video_call.dart'; class PatientProfileScreen extends StatefulWidget { @override @@ -59,7 +64,8 @@ class _PatientProfileScreenState extends State with Single late TabController _tabController; int index = 0; int _activeTab = 0; - +String noteValidation =""; + TextEditingController noteController = TextEditingController(); late StreamController videoCallDurationStreamController; late Stream videoCallDurationStream; @@ -370,18 +376,33 @@ class _PatientProfileScreenState extends State with Single Utils.showErrorToast(model.error); } else { await model.getDoctorProfile(); - patient.appointmentNo = int.parse(model.startCallRes!.appointmentNo.toString()); + patient.appointmentNo = int.parse(model.startCallRes!.appointmentNo.toString()); patient.episodeNo = 0; - model.updateInCallPatient(patient: patient, appointmentNo: int.parse(model.startCallRes!.appointmentNo.toString())); + model.updateInCallPatient(patient: patient, appointmentNo: int.parse(model.startCallRes!.appointmentNo.toString())); setState(() { isCallStarted = true; }); GifLoaderDialogUtils.hideDialog(context); - AppPermissionsUtils.requestVideoCallPermission( + // AppPermissionsUtils.requestVideoCallPermission( + // context: context, + // onTapGrant: () { + // locator() + // .openVideo(model.startCallRes!, patient, model.startCallRes != null ? model.startCallRes!.isRecording! : true, callConnected, callDisconnected); + // }); + // model.startCallRes!.openTokenID! + AppPermissionsUtils.requestVideoCallPermission( context: context, onTapGrant: () { - locator() - .openVideo(model.startCallRes!, patient, model.startCallRes != null ? model.startCallRes!.isRecording! : true, callConnected, callDisconnected); + Navigator.pushNamed( + context, + "zoom-call", + arguments: CallArguments(model.startCallRes!.openSessionID!, "123", "doctor", "40", "0", true,1), + ).then((completion) { + Future.delayed(const Duration(seconds: 1)) + .then((_) async { + _showEndCallPopup(context, model, patient, patient.vcId!); + }); + }); }); } } @@ -400,7 +421,65 @@ class _PatientProfileScreenState extends State with Single ), ); } + void _showEndCallPopup(BuildContext context,LiveCarePatientViewModel model, PatiantInformtion patient, int vcID) { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + + title: Text('Call Ended'), + content:SizedBox( + height: 150, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + AppTextFieldCustom(onChanged: (val){}, onFieldSubmitted: (){}, hintText: "Please enter Notes", height: 100, validationError: noteValidation, controller: noteController, ), + + + ],)), + actions: [ + AppButton( + onPressed: () { + _transferToAdmin(model, patient, vcID); + }, + title: 'Transfer to Admin', + color: Colors.grey, + fontColor: Colors.white, + fontSize: 14, + ), + SizedBox(height: 15,), + AppButton( + onPressed: () { + _endWithCharge(model, patient, vcID); + }, + title: 'End with Charge', + color: Colors.red, + fontColor: Colors.white, + fontSize: 14, + ) + ], + ); + }, + ); + + } + _transferToAdmin(LiveCarePatientViewModel model, PatiantInformtion patient, int vcID){ + if(noteController.text.isEmpty){ + noteValidation ='Please Enter Notes'; + setState(() { + + }); + }else { + model.transferToAdmin(vcID,noteController.text ); + Navigator.of(context).pop(); + } + } + _endWithCharge(LiveCarePatientViewModel model, PatiantInformtion patient, int vcID){ + model.endCallWithCharge(vcID, true); + Navigator.of(context).pop(); + } createEpisode({required PatiantInformtion patient, required SOAPViewModel model}) async { await locator().logEvent( eventCategory: "Patient Profile", diff --git a/lib/screens/patients/profile/soap_update/subjective/allergies/update_allergies_widget.dart b/lib/screens/patients/profile/soap_update/subjective/allergies/update_allergies_widget.dart index e5ed2a53..6069e09e 100644 --- a/lib/screens/patients/profile/soap_update/subjective/allergies/update_allergies_widget.dart +++ b/lib/screens/patients/profile/soap_update/subjective/allergies/update_allergies_widget.dart @@ -1,8 +1,11 @@ import 'package:doctor_app_flutter/config/size_config.dart'; import 'package:doctor_app_flutter/core/viewModel/project_view_model.dart'; import 'package:doctor_app_flutter/core/model/SOAP/selected_items/my_selected_allergy.dart'; +import 'package:doctor_app_flutter/screens/patients/In_patient/NoData.dart'; import 'package:doctor_app_flutter/screens/patients/profile/soap_update/shared_soap_widgets/remark_text.dart'; import 'package:doctor_app_flutter/screens/patients/profile/soap_update/shared_soap_widgets/remove_button.dart'; +import 'package:doctor_app_flutter/screens/patients/profile/soap_update_vida_plus/subjective/allergies/reactions_selection.dart'; +import 'package:doctor_app_flutter/utils/extenstions_utils.dart'; import 'package:doctor_app_flutter/utils/utils.dart'; import 'package:doctor_app_flutter/utils/translations_delegate_base_utils.dart'; import 'package:doctor_app_flutter/widgets/shared/app_texts_widget.dart'; @@ -15,7 +18,7 @@ import 'add_allergies.dart'; // ignore: must_be_immutable class UpdateAllergiesWidget extends StatefulWidget { - List? myAllergiesList; + List? myAllergiesList =[]; UpdateAllergiesWidget({Key? key, this.myAllergiesList}); @@ -29,7 +32,7 @@ class _UpdateAllergiesWidgetState extends State { ProjectViewModel projectViewModel = Provider.of(context); changeAllState() { setState(() { - print(widget.myAllergiesList); + }); } @@ -44,7 +47,7 @@ class _UpdateAllergiesWidgetState extends State { SizedBox( height: 20, ), - Container( + widget.myAllergiesList !=null ? Container( margin: EdgeInsets.only(left: 15, right: 15, top: 15), child: Column( children: widget.myAllergiesList!.map((selectedAllergy) { @@ -110,7 +113,7 @@ class _UpdateAllergiesWidgetState extends State { ], ); }).toList()), - ) + ) : SizedBox() ], ); } diff --git a/lib/screens/patients/profile/soap_update/subjective/update_subjective_page.dart b/lib/screens/patients/profile/soap_update/subjective/update_subjective_page.dart index 20279db9..a7f415c1 100644 --- a/lib/screens/patients/profile/soap_update/subjective/update_subjective_page.dart +++ b/lib/screens/patients/profile/soap_update/subjective/update_subjective_page.dart @@ -206,7 +206,7 @@ class _UpdateSubjectivePageState extends State implements }, child: Column( children: [ - UpdateAllergiesWidget(), + UpdateAllergiesWidget(myAllergiesList: myAllergiesList,), SizedBox( height: 30, ), diff --git a/lib/screens/patients/profile/soap_update_vida_plus/subjective/allergies/master_key_checkbox_search_allergies_widget.dart b/lib/screens/patients/profile/soap_update_vida_plus/subjective/allergies/master_key_checkbox_search_allergies_widget.dart index 3ee99a12..a5d1c693 100644 --- a/lib/screens/patients/profile/soap_update_vida_plus/subjective/allergies/master_key_checkbox_search_allergies_widget.dart +++ b/lib/screens/patients/profile/soap_update_vida_plus/subjective/allergies/master_key_checkbox_search_allergies_widget.dart @@ -120,9 +120,10 @@ class _MasterKeyCheckboxSearchAllergiesWidgetState color: Color(0xffD02127), ), style: ButtonStyle( - iconColor: - WidgetStateProperty.all< - Color>(Color(0xffD02127)), + + // iconColor: + // WidgetStateProperty.all< + // Color>()), ), onPressed: () { openReaction( diff --git a/lib/screens/patients/profile/soap_update_vida_plus/subjective/allergies/reactions_selection.dart b/lib/screens/patients/profile/soap_update_vida_plus/subjective/allergies/reactions_selection.dart index 6f4f348c..a3e3d43a 100644 --- a/lib/screens/patients/profile/soap_update_vida_plus/subjective/allergies/reactions_selection.dart +++ b/lib/screens/patients/profile/soap_update_vida_plus/subjective/allergies/reactions_selection.dart @@ -78,9 +78,9 @@ class _ReactionsSelectionAllergiesWidgetState controller: controllers![index], key: Key(index.toString()), - dense: false, - enableFeedback: false, - showTrailingIcon: false, + // dense: false, + // enableFeedback: false, + // showTrailingIcon: false, initiallyExpanded: widget .mySelectedAllergy! .allergyReactionDTOs![index] @@ -234,9 +234,9 @@ class _ReactionsSelectionAllergiesWidgetState return Column(children: [ ExpansionTile( key: Key(index.toString()), - dense: false, - enableFeedback: false, - showTrailingIcon: false, + // dense: false, + // enableFeedback: false, + // showTrailingIcon: false, initiallyExpanded: widget .editSelectedAllergy! .patientsAllergyReactionsDTOs![index] diff --git a/lib/screens/prescription/new_prescriptions_page.dart b/lib/screens/prescription/new_prescriptions_page.dart index d21b1cbe..c69d50f6 100644 --- a/lib/screens/prescription/new_prescriptions_page.dart +++ b/lib/screens/prescription/new_prescriptions_page.dart @@ -29,6 +29,7 @@ class NewPrescriptionsPage extends StatelessWidget { onModelReady: (model) async { await model.getPrescriptionListNew(mrn: patient.patientMRN, appNo: patient.appointmentNo == null ? 0 : int.parse(patient.appointmentNo.toString())); await model.isPrincipalCovered(patient: patient); + }, builder: (_, model, w) => AppScaffold( baseViewModel: model, 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..fc2bde01 --- /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/images/zoom/muted@2x.png") : const AssetImage("assets/images/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/images/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/images/zoom/muted@2x.png") : const AssetImage("assets/images/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..747a3a91 --- /dev/null +++ b/lib/screens/video_call_zoom/zoom_video_call.dart @@ -0,0 +1,1606 @@ + +import 'dart:async'; +import 'dart:convert'; +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(false); + 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": false, "autoAdjustSpeakerVolume": false}; + Map SDKvideoOptions = { + "localVideoOn": args.callType == 1 ? true : false, + }; + 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, true); + // 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.SessionDisconnecting) { + 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/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/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/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: 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; + final int callType; + + CallArguments(this.sessionName, this.sessionPwd, this.displayName, this.sessionIdleTimeoutMins, this.role, this.isJoin, this.callType); +} + +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); +} \ No newline at end of file diff --git a/lib/widgets/patients/patient_card/ShowTimer.dart b/lib/widgets/patients/patient_card/ShowTimer.dart index 8ed72425..e26bd3de 100644 --- a/lib/widgets/patients/patient_card/ShowTimer.dart +++ b/lib/widgets/patients/patient_card/ShowTimer.dart @@ -41,7 +41,7 @@ class _ShowTimerState extends State { Future.delayed(Duration(seconds: 1), () { if (this.remainingTime > 0) { - startShowTimer(); + startShowTimer(); } }); } diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index d34e9318..d9cacc88 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -14,7 +14,7 @@ import maps_launcher import path_provider_foundation import shared_preferences_foundation import speech_to_text_macos -import sqflite +import sqflite_darwin import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {