From 693f57a13e95f2f2d312dc18a2e1da49aab9860e Mon Sep 17 00:00:00 2001 From: Zohaib Iqbal Kambrani <> Date: Wed, 9 Jun 2021 12:53:40 +0300 Subject: [PATCH 01/30] Merge branch 'development' into ZohaibKambrani * development: (102 commits) prescription changes pescription changes fix imports project refactoring working on feature medical profile prescription changes hot fix hot fix hot fix refactoring hot fix hot fix add prescription form changes Add Medical Report Page hot fix hot fix add prescription changes fix in note and AddReplayOnReferralPatient Add Prescription changes prescription changes ... # Conflicts: # lib/screens/patients/DischargedPatientPage.dart # lib/screens/patients/ReferralDischargedPatientDetails.dart # lib/screens/patients/insurance_approvals_details.dart # lib/screens/patients/patients_screen.dart # lib/screens/patients/profile/note/progress_note_screen.dart # lib/screens/patients/profile/referral/referral_patient_detail_in-paint.dart # lib/screens/prescription/update_prescription_form.dart # lib/screens/sick-leave/add-sickleave.dart # lib/widgets/patients/PatientCard.dart # pubspec.lock --- ios/Podfile.lock | 327 ----------- ios/Runner.xcodeproj/project.pbxproj | 44 +- ios/Runner/AppDelegate.swift | 31 +- ios/Runner/Assets.xcassets/Contents.json | 6 +- .../end_call.imageset/Contents.json | 21 + .../end_call.imageset/end_call.png | Bin 0 -> 21324 bytes .../expand_video.imageset/Contents.json | 21 + .../expand_video.imageset/expand.png | Bin 0 -> 5366 bytes .../float_video.imageset/Contents.json | 21 + .../float_video.imageset/Group 8024.png | Bin 0 -> 4953 bytes .../mic_mute.imageset/Contents.json | 21 + .../mic_mute.imageset/mic_mute.png | Bin 0 -> 6537 bytes .../mic_unmute.imageset/Contents.json | 21 + .../mic_unmute.imageset/Group 8020.png | Bin 0 -> 5904 bytes .../video_mute.imageset/Contents.json | 21 + .../video_mute.imageset/video_mute.png | Bin 0 -> 6438 bytes .../video_unmute.imageset/Contents.json | 21 + .../video_unmute.imageset/Group 8022.png | Bin 0 -> 5715 bytes ios/Runner/Base.lproj/Main.storyboard | 515 ++++++++++++------ ios/Runner/MainAppViewController.swift | 142 +++++ ios/Runner/VCEmbeder.swift | 71 +++ ios/Runner/VideoCallRequestParameters.swift | 27 + ...er.swift => VideoCallViewController.swift} | 177 +++--- lib/client/base_app_client.dart | 2 +- .../patient_profile_screen.dart | 102 ++-- pubspec.lock | 6 +- 26 files changed, 950 insertions(+), 647 deletions(-) delete mode 100644 ios/Podfile.lock create mode 100644 ios/Runner/Assets.xcassets/end_call.imageset/Contents.json create mode 100644 ios/Runner/Assets.xcassets/end_call.imageset/end_call.png create mode 100644 ios/Runner/Assets.xcassets/expand_video.imageset/Contents.json create mode 100644 ios/Runner/Assets.xcassets/expand_video.imageset/expand.png create mode 100644 ios/Runner/Assets.xcassets/float_video.imageset/Contents.json create mode 100644 ios/Runner/Assets.xcassets/float_video.imageset/Group 8024.png create mode 100644 ios/Runner/Assets.xcassets/mic_mute.imageset/Contents.json create mode 100644 ios/Runner/Assets.xcassets/mic_mute.imageset/mic_mute.png create mode 100644 ios/Runner/Assets.xcassets/mic_unmute.imageset/Contents.json create mode 100644 ios/Runner/Assets.xcassets/mic_unmute.imageset/Group 8020.png create mode 100644 ios/Runner/Assets.xcassets/video_mute.imageset/Contents.json create mode 100644 ios/Runner/Assets.xcassets/video_mute.imageset/video_mute.png create mode 100644 ios/Runner/Assets.xcassets/video_unmute.imageset/Contents.json create mode 100644 ios/Runner/Assets.xcassets/video_unmute.imageset/Group 8022.png create mode 100644 ios/Runner/MainAppViewController.swift create mode 100644 ios/Runner/VCEmbeder.swift create mode 100644 ios/Runner/VideoCallRequestParameters.swift rename ios/Runner/{VideoViewController.swift => VideoCallViewController.swift} (84%) diff --git a/ios/Podfile.lock b/ios/Podfile.lock deleted file mode 100644 index 44151727..00000000 --- a/ios/Podfile.lock +++ /dev/null @@ -1,327 +0,0 @@ -PODS: - - Alamofire (5.4.3) - - barcode_scan_fix (0.0.1): - - Flutter - - MTBBarcodeScanner - - connectivity (0.0.1): - - Flutter - - Reachability - - connectivity_for_web (0.1.0): - - Flutter - - connectivity_macos (0.0.1): - - Flutter - - device_info (0.0.1): - - Flutter - - Firebase/CoreOnly (6.33.0): - - FirebaseCore (= 6.10.3) - - Firebase/Messaging (6.33.0): - - Firebase/CoreOnly - - FirebaseMessaging (~> 4.7.0) - - firebase_core (0.5.3): - - Firebase/CoreOnly (~> 6.33.0) - - Flutter - - firebase_core_web (0.1.0): - - Flutter - - firebase_messaging (7.0.3): - - Firebase/CoreOnly (~> 6.33.0) - - Firebase/Messaging (~> 6.33.0) - - firebase_core - - Flutter - - FirebaseCore (6.10.3): - - FirebaseCoreDiagnostics (~> 1.6) - - GoogleUtilities/Environment (~> 6.7) - - GoogleUtilities/Logger (~> 6.7) - - FirebaseCoreDiagnostics (1.7.0): - - GoogleDataTransport (~> 7.4) - - GoogleUtilities/Environment (~> 6.7) - - GoogleUtilities/Logger (~> 6.7) - - nanopb (~> 1.30906.0) - - FirebaseInstallations (1.7.0): - - FirebaseCore (~> 6.10) - - GoogleUtilities/Environment (~> 6.7) - - GoogleUtilities/UserDefaults (~> 6.7) - - PromisesObjC (~> 1.2) - - FirebaseInstanceID (4.8.0): - - FirebaseCore (~> 6.10) - - FirebaseInstallations (~> 1.6) - - GoogleUtilities/Environment (~> 6.7) - - GoogleUtilities/UserDefaults (~> 6.7) - - FirebaseMessaging (4.7.1): - - FirebaseCore (~> 6.10) - - FirebaseInstanceID (~> 4.7) - - GoogleUtilities/AppDelegateSwizzler (~> 6.7) - - GoogleUtilities/Environment (~> 6.7) - - GoogleUtilities/Reachability (~> 6.7) - - GoogleUtilities/UserDefaults (~> 6.7) - - Protobuf (>= 3.9.2, ~> 3.9) - - Flutter (1.0.0) - - flutter_flexible_toast (0.0.1): - - Flutter - - flutter_inappwebview (0.0.1): - - Flutter - - flutter_plugin_android_lifecycle (0.0.1): - - Flutter - - GoogleDataTransport (7.5.1): - - nanopb (~> 1.30906.0) - - GoogleUtilities/AppDelegateSwizzler (6.7.2): - - GoogleUtilities/Environment - - GoogleUtilities/Logger - - GoogleUtilities/Network - - GoogleUtilities/Environment (6.7.2): - - PromisesObjC (~> 1.2) - - GoogleUtilities/Logger (6.7.2): - - GoogleUtilities/Environment - - GoogleUtilities/Network (6.7.2): - - GoogleUtilities/Logger - - "GoogleUtilities/NSData+zlib" - - GoogleUtilities/Reachability - - "GoogleUtilities/NSData+zlib (6.7.2)" - - GoogleUtilities/Reachability (6.7.2): - - GoogleUtilities/Logger - - GoogleUtilities/UserDefaults (6.7.2): - - GoogleUtilities/Logger - - hexcolor (0.0.1): - - Flutter - - imei_plugin (0.0.1): - - Flutter - - local_auth (0.0.1): - - Flutter - - maps_launcher (0.0.1): - - Flutter - - MTBBarcodeScanner (5.0.11) - - nanopb (1.30906.0): - - nanopb/decode (= 1.30906.0) - - nanopb/encode (= 1.30906.0) - - nanopb/decode (1.30906.0) - - nanopb/encode (1.30906.0) - - OpenTok (2.15.3) - - path_provider_linux (0.0.1): - - Flutter - - path_provider_windows (0.0.1): - - Flutter - - "permission_handler (5.1.0+2)": - - Flutter - - PromisesObjC (1.2.12) - - Protobuf (3.17.0) - - Reachability (3.2) - - screen (0.0.1): - - Flutter - - shared_preferences (0.0.1): - - Flutter - - shared_preferences_linux (0.0.1): - - Flutter - - shared_preferences_macos (0.0.1): - - Flutter - - shared_preferences_web (0.0.1): - - Flutter - - shared_preferences_windows (0.0.1): - - Flutter - - speech_to_text (0.0.1): - - Flutter - - Try - - Try (2.1.1) - - url_launcher (0.0.1): - - Flutter - - url_launcher_linux (0.0.1): - - Flutter - - url_launcher_macos (0.0.1): - - Flutter - - url_launcher_web (0.0.1): - - Flutter - - url_launcher_windows (0.0.1): - - Flutter - - video_player (0.0.1): - - Flutter - - video_player_web (0.0.1): - - Flutter - - wakelock (0.0.1): - - Flutter - - webview_flutter (0.0.1): - - Flutter - -DEPENDENCIES: - - Alamofire (~> 5.2) - - barcode_scan_fix (from `.symlinks/plugins/barcode_scan_fix/ios`) - - connectivity (from `.symlinks/plugins/connectivity/ios`) - - connectivity_for_web (from `.symlinks/plugins/connectivity_for_web/ios`) - - connectivity_macos (from `.symlinks/plugins/connectivity_macos/ios`) - - device_info (from `.symlinks/plugins/device_info/ios`) - - firebase_core (from `.symlinks/plugins/firebase_core/ios`) - - firebase_core_web (from `.symlinks/plugins/firebase_core_web/ios`) - - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) - - Flutter (from `Flutter`) - - flutter_flexible_toast (from `.symlinks/plugins/flutter_flexible_toast/ios`) - - flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`) - - flutter_plugin_android_lifecycle (from `.symlinks/plugins/flutter_plugin_android_lifecycle/ios`) - - hexcolor (from `.symlinks/plugins/hexcolor/ios`) - - imei_plugin (from `.symlinks/plugins/imei_plugin/ios`) - - local_auth (from `.symlinks/plugins/local_auth/ios`) - - maps_launcher (from `.symlinks/plugins/maps_launcher/ios`) - - OpenTok - - path_provider_linux (from `.symlinks/plugins/path_provider_linux/ios`) - - path_provider_windows (from `.symlinks/plugins/path_provider_windows/ios`) - - permission_handler (from `.symlinks/plugins/permission_handler/ios`) - - screen (from `.symlinks/plugins/screen/ios`) - - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) - - shared_preferences_linux (from `.symlinks/plugins/shared_preferences_linux/ios`) - - shared_preferences_macos (from `.symlinks/plugins/shared_preferences_macos/ios`) - - shared_preferences_web (from `.symlinks/plugins/shared_preferences_web/ios`) - - shared_preferences_windows (from `.symlinks/plugins/shared_preferences_windows/ios`) - - speech_to_text (from `.symlinks/plugins/speech_to_text/ios`) - - url_launcher (from `.symlinks/plugins/url_launcher/ios`) - - url_launcher_linux (from `.symlinks/plugins/url_launcher_linux/ios`) - - url_launcher_macos (from `.symlinks/plugins/url_launcher_macos/ios`) - - url_launcher_web (from `.symlinks/plugins/url_launcher_web/ios`) - - url_launcher_windows (from `.symlinks/plugins/url_launcher_windows/ios`) - - video_player (from `.symlinks/plugins/video_player/ios`) - - video_player_web (from `.symlinks/plugins/video_player_web/ios`) - - wakelock (from `.symlinks/plugins/wakelock/ios`) - - webview_flutter (from `.symlinks/plugins/webview_flutter/ios`) - -SPEC REPOS: - trunk: - - Alamofire - - Firebase - - FirebaseCore - - FirebaseCoreDiagnostics - - FirebaseInstallations - - FirebaseInstanceID - - FirebaseMessaging - - GoogleDataTransport - - GoogleUtilities - - MTBBarcodeScanner - - nanopb - - OpenTok - - PromisesObjC - - Protobuf - - Reachability - - Try - -EXTERNAL SOURCES: - barcode_scan_fix: - :path: ".symlinks/plugins/barcode_scan_fix/ios" - connectivity: - :path: ".symlinks/plugins/connectivity/ios" - connectivity_for_web: - :path: ".symlinks/plugins/connectivity_for_web/ios" - connectivity_macos: - :path: ".symlinks/plugins/connectivity_macos/ios" - device_info: - :path: ".symlinks/plugins/device_info/ios" - firebase_core: - :path: ".symlinks/plugins/firebase_core/ios" - firebase_core_web: - :path: ".symlinks/plugins/firebase_core_web/ios" - firebase_messaging: - :path: ".symlinks/plugins/firebase_messaging/ios" - Flutter: - :path: Flutter - flutter_flexible_toast: - :path: ".symlinks/plugins/flutter_flexible_toast/ios" - flutter_inappwebview: - :path: ".symlinks/plugins/flutter_inappwebview/ios" - flutter_plugin_android_lifecycle: - :path: ".symlinks/plugins/flutter_plugin_android_lifecycle/ios" - hexcolor: - :path: ".symlinks/plugins/hexcolor/ios" - imei_plugin: - :path: ".symlinks/plugins/imei_plugin/ios" - local_auth: - :path: ".symlinks/plugins/local_auth/ios" - maps_launcher: - :path: ".symlinks/plugins/maps_launcher/ios" - path_provider_linux: - :path: ".symlinks/plugins/path_provider_linux/ios" - path_provider_windows: - :path: ".symlinks/plugins/path_provider_windows/ios" - permission_handler: - :path: ".symlinks/plugins/permission_handler/ios" - screen: - :path: ".symlinks/plugins/screen/ios" - shared_preferences: - :path: ".symlinks/plugins/shared_preferences/ios" - shared_preferences_linux: - :path: ".symlinks/plugins/shared_preferences_linux/ios" - shared_preferences_macos: - :path: ".symlinks/plugins/shared_preferences_macos/ios" - shared_preferences_web: - :path: ".symlinks/plugins/shared_preferences_web/ios" - shared_preferences_windows: - :path: ".symlinks/plugins/shared_preferences_windows/ios" - speech_to_text: - :path: ".symlinks/plugins/speech_to_text/ios" - url_launcher: - :path: ".symlinks/plugins/url_launcher/ios" - url_launcher_linux: - :path: ".symlinks/plugins/url_launcher_linux/ios" - url_launcher_macos: - :path: ".symlinks/plugins/url_launcher_macos/ios" - url_launcher_web: - :path: ".symlinks/plugins/url_launcher_web/ios" - url_launcher_windows: - :path: ".symlinks/plugins/url_launcher_windows/ios" - video_player: - :path: ".symlinks/plugins/video_player/ios" - video_player_web: - :path: ".symlinks/plugins/video_player_web/ios" - wakelock: - :path: ".symlinks/plugins/wakelock/ios" - webview_flutter: - :path: ".symlinks/plugins/webview_flutter/ios" - -SPEC CHECKSUMS: - Alamofire: e447a2774a40c996748296fa2c55112fdbbc42f9 - barcode_scan_fix: 80dd65de55f27eec6591dd077c8b85f2b79e31f1 - connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467 - connectivity_for_web: 2b8584556930d4bd490d82b836bcf45067ce345b - connectivity_macos: e2e9731b6b22dda39eb1b128f6969d574460e191 - device_info: d7d233b645a32c40dfdc212de5cf646ca482f175 - Firebase: 8db6f2d1b2c5e2984efba4949a145875a8f65fe5 - firebase_core: 5d6a02f3d85acd5f8321c2d6d62877626a670659 - firebase_core_web: d501d8b946b60c8af265428ce483b0fff5ad52d1 - firebase_messaging: 0aea2cd5885b65e19ede58ee3507f485c992cc75 - FirebaseCore: d889d9e12535b7f36ac8bfbf1713a0836a3012cd - FirebaseCoreDiagnostics: 770ac5958e1372ce67959ae4b4f31d8e127c3ac1 - FirebaseInstallations: 466c7b4d1f58fe16707693091da253726a731ed2 - FirebaseInstanceID: bd3ffc24367f901a43c063b36c640b345a4a5dd1 - FirebaseMessaging: 5eca4ef173de76253352511aafef774caa1cba2a - Flutter: 0e3d915762c693b495b44d77113d4970485de6ec - flutter_flexible_toast: 0547e740cae0c33bb7c51bcd931233f4584e1143 - flutter_inappwebview: 69dfbac46157b336ffbec19ca6dfd4638c7bf189 - flutter_plugin_android_lifecycle: dc0b544e129eebb77a6bfb1239d4d1c673a60a35 - GoogleDataTransport: f56af7caa4ed338dc8e138a5d7c5973e66440833 - GoogleUtilities: 7f2f5a07f888cdb145101d6042bc4422f57e70b3 - hexcolor: fdfb9c4258ad96e949c2dbcdf790a62194b8aa89 - imei_plugin: cb1af7c223ac2d82dcd1457a7137d93d65d2a3cd - local_auth: 25938960984c3a7f6e3253e3f8d962fdd16852bd - maps_launcher: eae38ee13a9c3f210fa04e04bb4c073fa4c6ed92 - MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb - nanopb: 59317e09cf1f1a0af72f12af412d54edf52603fc - OpenTok: fde03ecc5ea31fe0a453242847c4ee1f47e1d735 - path_provider_linux: 4d630dc393e1f20364f3e3b4a2ff41d9674a84e4 - path_provider_windows: a2b81600c677ac1959367280991971cb9a1edb3b - permission_handler: ccb20a9fad0ee9b1314a52b70b76b473c5f8dab0 - PromisesObjC: 3113f7f76903778cf4a0586bd1ab89329a0b7b97 - Protobuf: 7327d4444215b5f18e560a97f879ff5503c4581c - Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 - screen: abd91ca7bf3426e1cc3646d27e9b2358d6bf07b0 - shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d - shared_preferences_linux: afefbfe8d921e207f01ede8b60373d9e3b566b78 - shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087 - shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9 - shared_preferences_windows: 36b76d6f54e76ead957e60b49e2f124b4cd3e6ae - speech_to_text: b43a7d99aef037bd758ed8e45d79bbac035d2dfe - Try: 5ef669ae832617b3cee58cb2c6f99fb767a4ff96 - url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef - url_launcher_linux: ac237cb7a8058736e4aae38bdbcc748a4b394cc0 - url_launcher_macos: fd7894421cd39320dce5f292fc99ea9270b2a313 - url_launcher_web: e5527357f037c87560776e36436bf2b0288b965c - url_launcher_windows: 683d7c283894db8d1914d3ab2223b20cc1ad95d5 - video_player: 9cc823b1d9da7e8427ee591e8438bfbcde500e6e - video_player_web: da8cadb8274ed4f8dbee8d7171b420dedd437ce7 - wakelock: 0d4a70faf8950410735e3f61fb15d517c8a6efc4 - webview_flutter: d2b4d6c66968ad042ad94cbb791f5b72b4678a96 - -PODFILE CHECKSUM: d0a3789a37635365b4345e456835ed9d30398217 - -COCOAPODS: 1.10.1 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index f0784432..0e446318 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -9,12 +9,15 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 29211E4225C172B700DD740D /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 29211E4125C172B700DD740D /* GoogleService-Info.plist */; }; + 300790FA266FB14B0052174C /* VCEmbeder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 300790F9266FB14B0052174C /* VCEmbeder.swift */; }; + 30F70E6C266F56FD005D8F8E /* MainAppViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30F70E6B266F56FD005D8F8E /* MainAppViewController.swift */; }; + 30F70E6F266F6509005D8F8E /* VideoCallRequestParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30F70E6E266F6509005D8F8E /* VideoCallRequestParameters.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - 9CE61EBD24AB366E008D68DD /* VideoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CE61EBC24AB366E008D68DD /* VideoViewController.swift */; }; + 9CE61EBD24AB366E008D68DD /* VideoCallViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CE61EBC24AB366E008D68DD /* VideoCallViewController.swift */; }; 9CE61ECD24ADBB4C008D68DD /* ICallProtocoll.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CE61ECC24ADBB4C008D68DD /* ICallProtocoll.swift */; }; B650DC3076E9D70CB188286A /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93A5F83B23AB032D1E096663 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ @@ -37,6 +40,9 @@ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 29211CD725C165D600DD740D /* RunnerRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerRelease.entitlements; sourceTree = ""; }; 29211E4125C172B700DD740D /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 300790F9266FB14B0052174C /* VCEmbeder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VCEmbeder.swift; sourceTree = ""; }; + 30F70E6B266F56FD005D8F8E /* MainAppViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainAppViewController.swift; sourceTree = ""; }; + 30F70E6E266F6509005D8F8E /* VideoCallRequestParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoCallRequestParameters.swift; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -50,7 +56,7 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 9CE61EBC24AB366E008D68DD /* VideoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoViewController.swift; sourceTree = ""; }; + 9CE61EBC24AB366E008D68DD /* VideoCallViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoCallViewController.swift; sourceTree = ""; }; 9CE61ECC24ADBB4C008D68DD /* ICallProtocoll.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ICallProtocoll.swift; sourceTree = ""; }; 9D4B7DB43C6A6C849D2387CE /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; E698D7B14B12DF768FE47A1A /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; @@ -68,6 +74,30 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 300790F8266FB0F10052174C /* helpers */ = { + isa = PBXGroup; + children = ( + 300790F9266FB14B0052174C /* VCEmbeder.swift */, + ); + name = helpers; + sourceTree = ""; + }; + 30F70E6A266F56C9005D8F8E /* controllers */ = { + isa = PBXGroup; + children = ( + 30F70E6B266F56FD005D8F8E /* MainAppViewController.swift */, + ); + name = controllers; + sourceTree = ""; + }; + 30F70E6D266F64F8005D8F8E /* Models */ = { + isa = PBXGroup; + children = ( + 30F70E6E266F6509005D8F8E /* VideoCallRequestParameters.swift */, + ); + name = Models; + sourceTree = ""; + }; 7D66D387293CE5376A07EC5F /* Pods */ = { isa = PBXGroup; children = ( @@ -111,6 +141,9 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + 300790F8266FB0F10052174C /* helpers */, + 30F70E6D266F64F8005D8F8E /* Models */, + 30F70E6A266F56C9005D8F8E /* controllers */, 29211E4125C172B700DD740D /* GoogleService-Info.plist */, 29211CD725C165D600DD740D /* RunnerRelease.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, @@ -122,7 +155,7 @@ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, - 9CE61EBC24AB366E008D68DD /* VideoViewController.swift */, + 9CE61EBC24AB366E008D68DD /* VideoCallViewController.swift */, 9CE61ECC24ADBB4C008D68DD /* ICallProtocoll.swift */, ); path = Runner; @@ -290,10 +323,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 300790FA266FB14B0052174C /* VCEmbeder.swift in Sources */, + 30F70E6F266F6509005D8F8E /* VideoCallRequestParameters.swift in Sources */, 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, - 9CE61EBD24AB366E008D68DD /* VideoViewController.swift in Sources */, + 9CE61EBD24AB366E008D68DD /* VideoCallViewController.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 9CE61ECD24ADBB4C008D68DD /* ICallProtocoll.swift in Sources */, + 30F70E6C266F56FD005D8F8E /* MainAppViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index b97c7000..dc938e36 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -22,34 +22,8 @@ import OpenTok _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - let controller : FlutterViewController = window?.rootViewController as! FlutterViewController - let videoCallChannel = FlutterMethodChannel(name: "Dr.cloudSolution/videoCall", - binaryMessenger: controller.binaryMessenger) - videoCallChannel.setMethodCallHandler({ - (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in - self.result = result - switch call.method { - case "openVideoCall": - do { - let arguments = call.arguments as? NSDictionary - let kApiKey = arguments!["kApiKey"] as? String - let kSessionId = arguments!["kSessionId"] as? String - let kToken = arguments!["kToken"] as? String - let appLang = arguments!["appLang"] as? String - let vC_ID = arguments!["VC_ID"] as? Int - let tokenID = arguments!["TokenID"] as? String - let generalId = arguments!["generalId"] as? String - let doctorId = arguments!["DoctorId"] as? Int - let baseUrl = arguments!["baseUrl"] as? String - - self.openVideoChat(result: result,kApiKey: kApiKey!,kSessionId:kSessionId!,kToken: kToken!, appLang: appLang!, vC_ID: vC_ID!,tokenID: tokenID!,generalId: generalId!,doctorId: doctorId!, baseUrl: baseUrl!) - } - default: - result(FlutterMethodNotImplemented) - } - }) - GeneratedPluginRegistrant.register(with: self) + GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } @@ -61,7 +35,7 @@ import OpenTok let storyboard = UIStoryboard(name: "Main", bundle: nil) let identifier = "ViewControllerNav" let navVC = storyboard.instantiateViewController(withIdentifier: identifier) as! UINavigationController - let videoVC = navVC.viewControllers.first as! ViewController + let videoVC = navVC.viewControllers.first as! VideoCallViewController videoVC.kApiKey=kApiKey videoVC.kSessionId=kSessionId videoVC.kToken=kToken @@ -78,3 +52,4 @@ import OpenTok } } + diff --git a/ios/Runner/Assets.xcassets/Contents.json b/ios/Runner/Assets.xcassets/Contents.json index da4a164c..73c00596 100755 --- a/ios/Runner/Assets.xcassets/Contents.json +++ b/ios/Runner/Assets.xcassets/Contents.json @@ -1,6 +1,6 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/ios/Runner/Assets.xcassets/end_call.imageset/Contents.json b/ios/Runner/Assets.xcassets/end_call.imageset/Contents.json new file mode 100644 index 00000000..3762a5af --- /dev/null +++ b/ios/Runner/Assets.xcassets/end_call.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "end_call.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Runner/Assets.xcassets/end_call.imageset/end_call.png b/ios/Runner/Assets.xcassets/end_call.imageset/end_call.png new file mode 100644 index 0000000000000000000000000000000000000000..52f8e267234d686e31be8a6e1b44287ab98364db GIT binary patch literal 21324 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 F@_+AV*hT;V literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/expand_video.imageset/Contents.json b/ios/Runner/Assets.xcassets/expand_video.imageset/Contents.json new file mode 100644 index 00000000..1ecfeae9 --- /dev/null +++ b/ios/Runner/Assets.xcassets/expand_video.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "expand.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Runner/Assets.xcassets/expand_video.imageset/expand.png b/ios/Runner/Assets.xcassets/expand_video.imageset/expand.png new file mode 100644 index 0000000000000000000000000000000000000000..7020dc2ef8e2f6f35465c65c06836422cda0fd91 GIT binary patch literal 5366 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 PzPNO?j5Ob>+lTxQOg$37 literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/mic_mute.imageset/Contents.json b/ios/Runner/Assets.xcassets/mic_mute.imageset/Contents.json new file mode 100644 index 00000000..2a0f4d2e --- /dev/null +++ b/ios/Runner/Assets.xcassets/mic_mute.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "mic_mute.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Runner/Assets.xcassets/mic_mute.imageset/mic_mute.png b/ios/Runner/Assets.xcassets/mic_mute.imageset/mic_mute.png new file mode 100644 index 0000000000000000000000000000000000000000..266cb6c72418b5aba18b32c421c2b4e75f05e47e GIT binary patch literal 6537 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 Becu28 literal 0 HcmV?d00001 diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard index 73e18f76..17833007 100755 --- a/ios/Runner/Base.lproj/Main.storyboard +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -1,16 +1,16 @@ - + - + - + - + @@ -23,12 +23,12 @@ - + - + - + @@ -37,252 +37,451 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - + + + + + + - - - - - - - - - - - - - - - - - + - + + + + - + + @@ -292,5 +491,7 @@ + + diff --git a/ios/Runner/MainAppViewController.swift b/ios/Runner/MainAppViewController.swift new file mode 100644 index 00000000..c6466343 --- /dev/null +++ b/ios/Runner/MainAppViewController.swift @@ -0,0 +1,142 @@ +// +// MainAppViewController.swift +// Runner +// +// Created by Zohaib Iqbal Kambrani on 08/06/2021. +// Copyright © 2021 The Chromium Authors. All rights reserved. +// + +import Foundation + +class MainAppViewController: FlutterViewController{ + var videoCallContainer:UIView! + var videoCallViewController:VideoCallViewController! + var videoCallFlutterResult:FlutterResult? + var vdoCallViewMinConstraint:[NSLayoutConstraint]! + var vdoCallViewMaxConstraint:[NSLayoutConstraint]! + + + override func viewDidLoad() { + super.viewDidLoad() + initFlutterBridge() + prepareVideoCallView() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + } + + private func initFlutterBridge(){ + let videoCallChannel = FlutterMethodChannel(name: "Dr.cloudSolution/videoCall", binaryMessenger: binaryMessenger) + videoCallChannel.setMethodCallHandler({ + (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in + switch call.method { + case "openVideoCall": + self.startVideoCall(result: result, call: call) + default: + result(FlutterMethodNotImplemented) + } + }) + } + +} + + +// Video Call Functions +extension MainAppViewController : ICallProtocol{ + + func prepareVideoCallView(){ + videoCallContainer = UIView(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height)) + videoCallContainer.alpha = 0.0 + videoCallContainer.backgroundColor = UIColor.black + view.addSubview(videoCallContainer) + setVideoViewConstrints() + NSLayoutConstraint.activate(vdoCallViewMaxConstraint) + NSLayoutConstraint.deactivate(vdoCallViewMinConstraint) + + ViewEmbedder.embed( + withIdentifier: "videoCall", // Storyboard ID + parent: self, + container: self.videoCallContainer){ vc in + self.videoCallViewController = vc as? VideoCallViewController + + } + } + + private func startVideoCall(result: @escaping FlutterResult, call:FlutterMethodCall) { + videoCallFlutterResult = result + + if let arguments = call.arguments as? NSDictionary{ + showVideoCallView(true) + + videoCallViewController.onMinimize = { min in + self.minimizeVideoCall(min) + } + videoCallViewController.onClose = videoCallClosed + videoCallViewController.callBack = self + videoCallViewController.start(params: VideoCallRequestParameters(dictionary: arguments)) + } + + + } + + private func minimizeVideoCall(_ value:Bool){ + UIView.animate(withDuration: 0.5) { + if(value){ + NSLayoutConstraint.deactivate(self.vdoCallViewMaxConstraint) + NSLayoutConstraint.activate(self.vdoCallViewMinConstraint) + }else{ + NSLayoutConstraint.deactivate(self.vdoCallViewMinConstraint) + NSLayoutConstraint.activate(self.vdoCallViewMaxConstraint) + } + self.videoCallContainer.layer.cornerRadius = value ? 10 : 0 + self.videoCallContainer.layer.borderColor = value ? UIColor.white.cgColor : nil + self.videoCallContainer.layer.borderWidth = value ? 2 : 0 + self.view.layoutIfNeeded() + } + } + + private func showVideoCallView(_ value:Bool){ + UIView.animate(withDuration: 0.5) { + self.videoCallContainer.alpha = value ? 1.0 : 0.0 + } completion: { complete in + if(value == false){ + self.videoCallContainer.removeFromSuperview() + } + } + } + + private func videoCallClosed(){ + showVideoCallView(false) + } + + func sessionDone(res: Any) { + videoCallFlutterResult?(res) + } + + func sessionNotResponded(res: Any) { + videoCallFlutterResult?(res) + } + + + func setVideoViewConstrints(){ + let screen = UIScreen.main.bounds + + + videoCallContainer.translatesAutoresizingMaskIntoConstraints = false + + vdoCallViewMinConstraint = [ + videoCallContainer.topAnchor.constraint(equalTo: view.topAnchor, constant: 20), + videoCallContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), + videoCallContainer.widthAnchor.constraint(equalToConstant: screen.width/3), + videoCallContainer.heightAnchor.constraint(equalToConstant: screen.height/3.5) + ] + vdoCallViewMaxConstraint = [ + videoCallContainer.topAnchor.constraint(equalTo: view.topAnchor), + videoCallContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor), + videoCallContainer.widthAnchor.constraint(equalToConstant: screen.width), + videoCallContainer.heightAnchor.constraint(equalToConstant: screen.height) + ] + } + +} diff --git a/ios/Runner/VCEmbeder.swift b/ios/Runner/VCEmbeder.swift new file mode 100644 index 00000000..e86dde75 --- /dev/null +++ b/ios/Runner/VCEmbeder.swift @@ -0,0 +1,71 @@ +// +// VCEmbeder.swift +// Runner +// +// Created by Zohaib Iqbal Kambrani on 08/06/2021. +// Copyright © 2021 The Chromium Authors. All rights reserved. +// + +import Foundation + +extension UIView { + func fill(to parent: UIView) { + topAnchor.constraint(equalTo: parent.topAnchor).isActive = true + leadingAnchor.constraint(equalTo: parent.leadingAnchor).isActive = true + bottomAnchor.constraint(equalTo: parent.bottomAnchor).isActive = true + trailingAnchor.constraint(equalTo: parent.trailingAnchor).isActive = true + } + + func fillToParent() { + if let parent = self.superview{ + topAnchor.constraint(equalTo: parent.topAnchor).isActive = true + leadingAnchor.constraint(equalTo: parent.leadingAnchor).isActive = true + bottomAnchor.constraint(equalTo: parent.bottomAnchor).isActive = true + trailingAnchor.constraint(equalTo: parent.trailingAnchor).isActive = true + } + } + + func fillTo(view:UIView) { + view.addSubview(self) + fill(to: view) + } +} + +class ViewEmbedder { + class func embed( + parent:UIViewController, + container:UIView, + child:UIViewController, + previous:UIViewController?){ + + if let previous = previous { + removeFromParent(vc: previous) + } + child.willMove(toParent: parent) + parent.addChild(child) + container.addSubview(child.view) + child.didMove(toParent: parent) + let w = container.frame.size.width; + let h = container.frame.size.height; + child.view.frame = CGRect(x: 0, y: 0, width: w, height: h) + + child.view.fill(to: container) + } + + class func removeFromParent(vc:UIViewController){ + vc.willMove(toParent: nil) + vc.view.removeFromSuperview() + vc.removeFromParent() + } + + class func embed(withIdentifier id:String, parent:UIViewController, container:UIView, completion:((UIViewController)->Void)? = nil){ + let vc = parent.storyboard!.instantiateViewController(withIdentifier: id) + embed( + parent: parent, + container: container, + child: vc, + previous: parent.children.first + ) + completion?(vc) + } +} diff --git a/ios/Runner/VideoCallRequestParameters.swift b/ios/Runner/VideoCallRequestParameters.swift new file mode 100644 index 00000000..2366c562 --- /dev/null +++ b/ios/Runner/VideoCallRequestParameters.swift @@ -0,0 +1,27 @@ + + +import Foundation + +class VideoCallRequestParameters{ + var apiKey:String? + var sessionId:String? + var token:String? + var lang:String? + var vcId:Int? + var tokenId:String? + var generalId:String? + var doctorId:Int? + var baseUrl:String? + + init(dictionary:NSDictionary){ + self.apiKey = dictionary["kApiKey"] as? String + self.sessionId = dictionary["kSessionId"] as? String + self.token = dictionary["kToken"] as? String + self.lang = dictionary["appLang"] as? String + self.vcId = dictionary["VC_ID"] as? Int + self.tokenId = dictionary["TokenID"] as? String + self.generalId = dictionary["generalId"] as? String + self.doctorId = dictionary["DoctorId"] as? Int + self.baseUrl = dictionary["baseUrl"] as? String + } +} diff --git a/ios/Runner/VideoViewController.swift b/ios/Runner/VideoCallViewController.swift similarity index 84% rename from ios/Runner/VideoViewController.swift rename to ios/Runner/VideoCallViewController.swift index 44e45f3a..a05957c9 100644 --- a/ios/Runner/VideoViewController.swift +++ b/ios/Runner/VideoCallViewController.swift @@ -11,7 +11,7 @@ import OpenTok import Alamofire -class ViewController: UIViewController { +class VideoCallViewController: UIViewController { var session: OTSession? var publisher: OTPublisher? @@ -34,18 +34,104 @@ class ViewController: UIViewController { var seconds = 30 var isUserConnect : Bool = false + var onMinimize:((Bool)->Void)? = nil + var onClose:(()->Void)? = nil + + // Bottom Actions + @IBOutlet weak var actionsHeightConstraint: NSLayoutConstraint! + @IBOutlet weak var videoMuteBtn: UIButton! + @IBOutlet weak var micMuteBtn: UIButton! + + @IBOutlet weak var localvideoTopMargin: NSLayoutConstraint! + @IBOutlet weak var localVideo: UIView! + @IBOutlet weak var remoteVideo: UIView! + @IBOutlet weak var controlButtons: UIView! + @IBOutlet weak var remoteVideoMutedIndicator: UIImageView! + @IBOutlet weak var localVideoMutedBg: UIView! override func viewDidLoad() { - super.viewDidLoad() + super.viewDidLoad() + localVideo.layer.borderColor = UIColor.white.cgColor + } + + @IBAction func didClickMuteButton(_ sender: UIButton) { + sender.isSelected = !sender.isSelected + publisher!.publishAudio = !sender.isSelected - setupButtons() - askForMicrophonePermission() - requestCameraPermissionsIfNeeded() - hideVideoMuted() - setupSession() - - } + } + @IBAction func didClickSpeakerButton(_ sender: UIButton) { + sender.isSelected = !sender.isSelected + subscriber?.subscribeToAudio = !sender.isSelected + // resetHideButtonsTimer() + } + + @IBAction func didClickVideoMuteButton(_ sender: UIButton) { + sender.isSelected = !sender.isSelected + if publisher!.publishVideo { + publisher!.publishVideo = false + } else { + publisher!.publishVideo = true + } + localVideo.isHidden = sender.isSelected + localVideoMutedBg.isHidden = !sender.isSelected + // resetHideButtonsTimer() + + } + + + @IBAction func didClickSwitchCameraButton(_ sender: UIButton) { + sender.isSelected = !sender.isSelected + if sender.isSelected { + publisher!.cameraPosition = AVCaptureDevice.Position.front + } else { + publisher!.cameraPosition = AVCaptureDevice.Position.back + } + /// resetHideButtonsTimer() + } + + @IBAction func hangUp(_ sender: UIButton) { + callBack?.sessionDone(res:["callResponse":"CallEnd"]) + sessionDisconnect() + } + + var minimized = false + @IBAction func onMinimize(_ sender: UIButton) { + minimized = !minimized + sender.isSelected = minimized + onMinimize?(minimized) + + self.videoMuteBtn.isHidden = minimized + self.micMuteBtn.isHidden = minimized + + let min_ = minimized + UIView.animate(withDuration: 1) { + self.actionsHeightConstraint.constant = min_ ? 30 : 60 + self.localvideoTopMargin.constant = min_ ? 20 : 40 + + let vdoBound = self.localVideo.bounds + self.publisher?.view?.frame = CGRect(x: 0, y: 0, width: vdoBound.size.width, height: vdoBound.size.height) + self.publisher?.view?.layoutIfNeeded() + } + } + + func start(params:VideoCallRequestParameters){ + + self.kApiKey = params.apiKey ?? "" + self.kSessionId = params.sessionId ?? "" + self.kToken = params.token ?? "" + self.VC_ID = params.vcId ?? 0 + self.generalid = params.generalId ?? "" + self.TokenID = params.tokenId ?? "" + self.DoctorId = params.doctorId ?? 0 + self.baseUrl = params.baseUrl ?? "" + + setupButtons() + askForMicrophonePermission() + requestCameraPermissionsIfNeeded() + hideVideoMuted() + setupSession() + } private func changeCallStatus(callStatus:Int){ let URL_USER_REGISTER = baseUrl+"LiveCareApi/DoctorApp/ChangeCallStatus" @@ -137,59 +223,15 @@ class ViewController: UIViewController { // display a useful message asking the user to grant permissions from within Settings > Privacy > Camera } - - @IBAction func didClickMuteButton(_ sender: UIButton) { - sender.isSelected = !sender.isSelected - publisher!.publishAudio = !sender.isSelected - - } - - @IBAction func didClickSpeakerButton(_ sender: UIButton) { - sender.isSelected = !sender.isSelected - subscriber?.subscribeToAudio = !sender.isSelected - // resetHideButtonsTimer() - } - - @IBAction func didClickVideoMuteButton(_ sender: UIButton) { - sender.isSelected = !sender.isSelected - if publisher!.publishVideo { - publisher!.publishVideo = false - } else { - publisher!.publishVideo = true - } - localVideo.isHidden = sender.isSelected - localVideoMutedBg.isHidden = !sender.isSelected - localVideoMutedIndicator.isHidden = !sender.isSelected - // resetHideButtonsTimer() - - } - - - @IBAction func didClickSwitchCameraButton(_ sender: UIButton) { - sender.isSelected = !sender.isSelected - if sender.isSelected { - publisher!.cameraPosition = AVCaptureDevice.Position.front - } else { - publisher!.cameraPosition = AVCaptureDevice.Position.back - } - /// resetHideButtonsTimer() - } - - @IBAction func hangUp(_ sender: UIButton) { - callBack?.sessionDone(res:["callResponse":"CallEnd"]) - sessionDisconnect() - } - - func sessionDisconnect() { changeCallStatus(callStatus: 16) if (session != nil) { print("disconnecting....") session!.disconnect(nil) dismiss(animated: true) - return } dismiss(animated: true) + onClose?() } func requestCameraPermissionsIfNeeded() { @@ -226,7 +268,6 @@ class ViewController: UIViewController { func hideVideoMuted() { remoteVideoMutedIndicator.isHidden = true localVideoMutedBg.isHidden = true - localVideoMutedIndicator.isHidden = true } func setupSession() { @@ -267,19 +308,6 @@ class ViewController: UIViewController { - @IBOutlet weak var localVideo: UIView! - - @IBOutlet weak var remoteVideo: UIView! - - @IBOutlet weak var controlButtons: UIView! - - @IBOutlet weak var remoteVideoMutedIndicator: UIImageView! - - @IBOutlet weak var localVideoMutedBg: UIImageView! - - - @IBOutlet weak var localVideoMutedIndicator: UIImageView! - @objc func updateTimer(){ seconds -= 1 //This will decrement(count down)the seconds. print(seconds) @@ -293,7 +321,7 @@ class ViewController: UIViewController { } -extension ViewController: OTSessionDelegate { +extension VideoCallViewController: OTSessionDelegate { func sessionDidConnect(_ session: OTSession) { print("The client connected to the OpenTok session.") @@ -301,7 +329,7 @@ extension ViewController: OTSessionDelegate { - timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(ViewController.updateTimer)), userInfo: nil, repeats: true) + timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(VideoCallViewController.updateTimer)), userInfo: nil, repeats: true) setupPublisher() @@ -318,9 +346,8 @@ extension ViewController: OTSessionDelegate { if error != nil { showAlert(error?.localizedDescription) } - - publisher?.view!.frame = CGRect(x: localVideo.bounds.origin.x, y: localVideo.bounds.origin.y, width: localVideo.bounds.size.width, height: localVideo.bounds.size.height) - + + publisher?.view!.frame = CGRect(x: 0, y: 0, width: localVideo.bounds.size.width, height: localVideo.bounds.size.height) localVideo.addSubview((publisher?.view)!) } @@ -408,7 +435,7 @@ extension ViewController: OTSessionDelegate { } -extension ViewController: OTPublisherDelegate { +extension VideoCallViewController: OTPublisherDelegate { func publisher(_ publisher: OTPublisherKit, didFailWithError error: OTError) { print("The publisher failed: \(error)") } @@ -420,7 +447,7 @@ extension ViewController: OTPublisherDelegate { } } -extension ViewController: OTSubscriberDelegate { +extension VideoCallViewController: OTSubscriberDelegate { public func subscriberDidConnect(toStream subscriber: OTSubscriberKit) { print("The subscriber did connect to the stream.") } diff --git a/lib/client/base_app_client.dart b/lib/client/base_app_client.dart index 10181bf4..c011a4e1 100644 --- a/lib/client/base_app_client.dart +++ b/lib/client/base_app_client.dart @@ -109,7 +109,7 @@ class BaseAppClient { parsed['AndroidLink'], parsed['IOSLink']); } - if (!parsed['IsAuthenticated']) { + if (parsed['IsAuthenticated'] != null && !parsed['IsAuthenticated']) { if (body['OTP_SendType'] != null) { onFailure(getError(parsed), statusCode); } else if (!isAllowAny) { 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 55b2d6f9..30f53a0b 100644 --- a/lib/screens/patients/profile/profile_screen/patient_profile_screen.dart +++ b/lib/screens/patients/profile/profile_screen/patient_profile_screen.dart @@ -289,60 +289,64 @@ class _PatientProfileScreenState extends State EndCallScreen(patient:patient))); } else { GifLoaderDialogUtils.showMyDialog(context); - await model.startCall( isReCall : false, vCID: patient.vcId); + // await model.startCall( isReCall : false, vCID: patient.vcId); - if(model.state == ViewState.ErrorLocal) { - GifLoaderDialogUtils.hideDialog(context); - Helpers.showErrorToast(model.error); - } else { - await model.getDoctorProfile(); - patient.appointmentNo = model.startCallRes.appointmentNo; - patient.episodeNo = 0; + if(model.state == ViewState.ErrorLocal) { + GifLoaderDialogUtils.hideDialog(context); + Helpers.showErrorToast(model.error); + } else { + await model.getDoctorProfile(); + // patient.appointmentNo = model.startCallRes.appointmentNo; + patient.episodeNo = 0; - GifLoaderDialogUtils.hideDialog(context); - await VideoChannel.openVideoCallScreen( - kToken: model.startCallRes.openTokenID, - kSessionId: model.startCallRes.openSessionID, - kApiKey: '46209962', - vcId: patient.vcId, - tokenID: await model.getToken(), - generalId: GENERAL_ID, - doctorId: model.doctorProfile.doctorID, - onFailure: (String error) { - DrAppToastMsg.showErrorToast(error); - }, - onCallEnd: () { - WidgetsBinding.instance.addPostFrameCallback((_) { - GifLoaderDialogUtils.showMyDialog(context); - model.endCall(patient.vcId, false,).then((value) { - GifLoaderDialogUtils.hideDialog(context); - if (model.state == ViewState.ErrorLocal) { - DrAppToastMsg.showErrorToast(model.error); - } - setState(() { - isCallFinished = true; - }); - }); - }); - }, - onCallNotRespond: (SessionStatusModel sessionStatusModel) { - WidgetsBinding.instance.addPostFrameCallback((_) { - GifLoaderDialogUtils.showMyDialog(context); - model.endCall(patient.vcId, sessionStatusModel.sessionStatus == 3,).then((value) { - GifLoaderDialogUtils.hideDialog(context); - if (model.state == ViewState.ErrorLocal) { - DrAppToastMsg.showErrorToast(model.error); - } - setState(() { - isCallFinished = true; - }); - }); + GifLoaderDialogUtils.hideDialog(context); + await VideoChannel.openVideoCallScreen( + kToken: "T1==cGFydG5lcl9pZD00NzI0Nzk1NCZzaWc9NGIyZDljOTY3YjFiNWU1YzUzNzFmMjIyNjJmNmEzY2Y5NzZjOTdlYzpzZXNzaW9uX2lkPTFfTVg0ME56STBOemsxTkg1LU1UWXlNekEyTlRRMU9EVXhObjVrVFRoMFlVdFJXaXRYTWpadFZGZHFhSGxZVGpOdE1UVi1mZyZjcmVhdGVfdGltZT0xNjIzMDY1NDk1Jm5vbmNlPTAuMjM2Mjk0NTIwMTkyOTA4OTcmcm9sZT1wdWJsaXNoZXImZXhwaXJlX3RpbWU9MTYyNTY1NzQ5NCZpbml0aWFsX2xheW91dF9jbGFzc19saXN0PQ==",//model.startCallRes.openTokenID, + kSessionId:"1_MX40NzI0Nzk1NH5-MTYyMzA2NTQ1ODUxNn5kTTh0YUtRWitXMjZtVFdqaHlYTjNtMTV-fg",// model.startCallRes.openSessionID, + kApiKey: '47247954',//46209962 + vcId: patient.vcId, + tokenID: await model.getToken(), + generalId: GENERAL_ID, + doctorId: model.doctorProfile.doctorID, + onFailure: (String error) { + DrAppToastMsg.showErrorToast(error); + }, + onCallEnd: () { + var asd=""; + // WidgetsBinding.instance.addPostFrameCallback((_) { + // GifLoaderDialogUtils.showMyDialog(context); + // model.endCall(patient.vcId, false,).then((value) { + // GifLoaderDialogUtils.hideDialog(context); + // if (model.state == ViewState.ErrorLocal) { + // DrAppToastMsg.showErrorToast(model.error); + // } + // setState(() { + // isCallFinished = true; + // }); + // }); + // }); + }, + onCallNotRespond: (SessionStatusModel sessionStatusModel) { + var asd=""; + // WidgetsBinding.instance.addPostFrameCallback((_) { + // GifLoaderDialogUtils.showMyDialog(context); + // model.endCall(patient.vcId, sessionStatusModel.sessionStatus == 3,).then((value) { + // GifLoaderDialogUtils.hideDialog(context); + // if (model.state == ViewState.ErrorLocal) { + // DrAppToastMsg.showErrorToast(model.error); + // } + // setState(() { + // isCallFinished = true; + // }); + // }); + // + // }); + }); - }); - }); - } + } } + }, ), ), diff --git a/pubspec.lock b/pubspec.lock index 25596d43..77df9848 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -629,7 +629,7 @@ packages: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.4" + version: "1.3.0-nullsafety.3" mime: dependency: transitive description: @@ -921,7 +921,7 @@ packages: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.10.0-nullsafety.2" + version: "1.10.0-nullsafety.1" sticky_headers: dependency: "direct main" description: @@ -1119,5 +1119,5 @@ packages: source: hosted version: "2.2.1" sdks: - dart: ">=2.10.0 <=2.11.0-213.1.beta" + dart: ">=2.10.0 <2.11.0" flutter: ">=1.22.0 <2.0.0" From e76ecf172fe331c10acb068779fcefa65f5ae492 Mon Sep 17 00:00:00 2001 From: mosazaid Date: Wed, 9 Jun 2021 14:46:03 +0300 Subject: [PATCH 02/30] video-straming-android-changes --- android/app/build.gradle | 1 + .../main/kotlin/com/hmg/hmgDr/MainActivity.kt | 32 +- .../hmgDr/ui/fragment/VideoCallFragment.kt | 385 ++++++++++++++++++ android/app/src/main/res/values/strings.xml | 2 + pubspec.lock | 2 +- 5 files changed, 410 insertions(+), 12 deletions(-) create mode 100644 android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt diff --git a/android/app/build.gradle b/android/app/build.gradle index 16960d54..deb42f99 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -70,6 +70,7 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt index 612118c9..b95e7cb9 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt @@ -2,17 +2,19 @@ package com.hmg.hmgDr import android.app.Activity import android.content.Intent +import android.os.Bundle import androidx.annotation.NonNull +import com.google.gson.GsonBuilder import com.hmg.hmgDr.Model.GetSessionStatusModel import com.hmg.hmgDr.Model.SessionStatusModel -import com.hmg.hmgDr.ui.VideoCallActivity -import com.google.gson.GsonBuilder +import io.flutter.embedding.android.FlutterFragment import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugins.GeneratedPluginRegistrant + class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler { private val CHANNEL = "Dr.cloudSolution/videoCall" @@ -20,6 +22,7 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler private var call: MethodCall? = null private val LAUNCH_VIDEO: Int = 1 + private val flutterFragment: FlutterFragment? = null override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine) @@ -27,6 +30,11 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler(this) } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + } + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { @@ -59,15 +67,17 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler private fun openVideoCall(apiKey: String?, sessionId: String?, token: String?, appLang: String?, baseUrl: String?, sessionStatusModel: GetSessionStatusModel) { // val videoCallActivity = VideoCallActivity() - val intent = Intent(this, VideoCallActivity::class.java) - intent.putExtra("apiKey", apiKey) - intent.putExtra("sessionId", sessionId) - intent.putExtra("token", token) - intent.putExtra("appLang", appLang) - intent.putExtra("baseUrl", baseUrl) - intent.putExtra("sessionStatusModel", sessionStatusModel) - startActivityForResult(intent, LAUNCH_VIDEO) - +// val intent = Intent(this, VideoCallActivity::class.java) +// intent.putExtra("apiKey", apiKey) +// intent.putExtra("sessionId", sessionId) +// intent.putExtra("token", token) +// intent.putExtra("appLang", appLang) +// intent.putExtra("baseUrl", baseUrl) +// intent.putExtra("sessionStatusModel", sessionStatusModel) +// startActivityForResult(intent, LAUNCH_VIDEO) + + val transaction = supportFragmentManager.beginTransaction() +// transaction.add() } diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt new file mode 100644 index 00000000..be343b89 --- /dev/null +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt @@ -0,0 +1,385 @@ +package com.hmg.hmgDr.ui.fragment + +import android.Manifest +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Intent +import android.opengl.GLSurfaceView +import android.os.Bundle +import android.os.CountDownTimer +import android.os.Handler +import android.os.Looper +import android.util.Log +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.widget.* +import androidx.fragment.app.Fragment +import com.hmg.hmgDr.Model.ChangeCallStatusRequestModel +import com.hmg.hmgDr.Model.GetSessionStatusModel +import com.hmg.hmgDr.Model.SessionStatusModel +import com.hmg.hmgDr.R +import com.hmg.hmgDr.ui.VideoCallActivity +import com.hmg.hmgDr.ui.VideoCallContract.VideoCallPresenter +import com.hmg.hmgDr.ui.VideoCallContract.VideoCallView +import com.hmg.hmgDr.ui.VideoCallPresenterImpl +import com.opentok.android.* +import com.opentok.android.PublisherKit.PublisherListener +import pub.devrel.easypermissions.AfterPermissionGranted +import pub.devrel.easypermissions.AppSettingsDialog +import pub.devrel.easypermissions.EasyPermissions +import pub.devrel.easypermissions.EasyPermissions.PermissionCallbacks +import java.util.* + +class VideoCallFragment : Fragment(), PermissionCallbacks, Session.SessionListener, PublisherListener, + SubscriberKit.VideoListener, VideoCallView { + + lateinit var videoCallPresenter: VideoCallPresenter + + private var mSession: Session? = null + private var mPublisher: Publisher? = null + private var mSubscriber: Subscriber? = null + + private var mVolHandler: Handler? = null + private var mConnectedHandler: Handler? = null + private var mVolRunnable: Runnable? = null + private var mConnectedRunnable: Runnable? = null + + private var mPublisherViewContainer: FrameLayout? = null + private var mSubscriberViewContainer: RelativeLayout? = null + private var controlPanel: RelativeLayout? = null + + private var apiKey: String? = null + private var sessionId: String? = null + private var token: String? = null + private var appLang: String? = null + private var baseUrl: String? = null + + private var isSwitchCameraClicked = false + private var isCameraClicked = false + private var isSpeckerClicked = false + private var isMicClicked = false + + private var mCallBtn: ImageView? = null + private var mCameraBtn: ImageView? = null + private var mSwitchCameraBtn: ImageView? = null + private var mspeckerBtn: ImageView? = null + private var mMicBtn: ImageView? = null + + private val progressBar: ProgressBar? = null + private val countDownTimer: CountDownTimer? = null + private val progressBarTextView: TextView? = null + private val progressBarLayout: RelativeLayout? = null + + private var isConnected = false + + private var sessionStatusModel: GetSessionStatusModel? = null + + + override fun onCreate(savedInstanceState: Bundle?) { + requireActivity().setTheme(R.style.AppTheme) + super.onCreate(savedInstanceState) + + requestPermissions() + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + + val view = inflater.inflate(R.layout.activity_video_call, container, false) + + Objects.requireNonNull(requireActivity().actionBar)!!.hide() + initUI(view) + + return view + } + + override fun onPause() { + super.onPause() + if (mSession == null) { + return + } + mSession!!.onPause() + if (requireActivity().isFinishing) { + disconnectSession() + } + } + + override fun onResume() { + super.onResume() + if (mSession == null) { + return + } + mSession!!.onResume() + } + + override fun onDestroy() { + disconnectSession() + super.onDestroy() + } + + @SuppressLint("ClickableViewAccessibility") + private fun initUI(view: View) { + mPublisherViewContainer = view.findViewById(R.id.local_video_view_container) + mSubscriberViewContainer = view.findViewById(R.id.remote_video_view_container) + + arguments?.run { + apiKey = getString("apiKey") + sessionId = getString("sessionId") + token = getString("token") + appLang = getString("appLang") + baseUrl = getString("baseUrl") + sessionStatusModel = getParcelable("sessionStatusModel") + } + + controlPanel = view.findViewById(R.id.control_panel) + videoCallPresenter = VideoCallPresenterImpl(this, baseUrl) + mCallBtn = view.findViewById(R.id.btn_call) + mCameraBtn = view.findViewById(R.id.btn_camera) + mSwitchCameraBtn = view.findViewById(R.id.btn_switch_camera) + mspeckerBtn = view.findViewById(R.id.btn_specker) + mMicBtn = view.findViewById(R.id.btn_mic) + + // progressBarLayout=findViewById(R.id.progressBar); + // progressBar=findViewById(R.id.progress_bar); +// progressBarTextView=findViewById(R.id.progress_bar_text); +// progressBar.setVisibility(View.GONE); + hiddenButtons() + checkClientConnected() + mSubscriberViewContainer!!.setOnTouchListener { v: View?, event: MotionEvent? -> + controlPanel!!.visibility = View.VISIBLE + mVolHandler!!.removeCallbacks(mVolRunnable!!) + mVolHandler!!.postDelayed(mVolRunnable!!, (5 * 1000).toLong()) + true + } + if (appLang == "ar") { + progressBarLayout!!.layoutDirection = View.LAYOUT_DIRECTION_RTL + } + } + + private fun checkClientConnected() { + mConnectedHandler = Handler((Looper.getMainLooper())) + mConnectedRunnable = Runnable { + if (!isConnected) { + videoCallPresenter.callClintConnected(sessionStatusModel) + } + } + mConnectedHandler!!.postDelayed(mConnectedRunnable!!, (55 * 1000).toLong()) + } + + private fun hiddenButtons() { + mVolHandler = Handler() + mVolRunnable = Runnable { controlPanel!!.visibility = View.GONE } + mVolHandler!!.postDelayed(mVolRunnable!!, (5 * 1000).toLong()) + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) + } + + override fun onPermissionsGranted(requestCode: Int, perms: List) { + Log.d(TAG, "onPermissionsGranted:" + requestCode + ":" + perms.size) + } + + override fun onPermissionsDenied(requestCode: Int, perms: List) { + Log.d(TAG, "onPermissionsDenied:" + requestCode + ":" + perms.size) + if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) { + AppSettingsDialog.Builder(this) + .setTitle(getString(R.string.title_settings_dialog)) + .setRationale(getString(R.string.rationale_ask_again)) + .setPositiveButton(getString(R.string.setting)) + .setNegativeButton(getString(R.string.cancel)) + .setRequestCode(RC_SETTINGS_SCREEN_PERM) + .build() + .show() + } + } + + @AfterPermissionGranted(RC_VIDEO_APP_PERM) + private fun requestPermissions() { + val perms = arrayOf(Manifest.permission.INTERNET, Manifest.permission.CAMERA) + if (EasyPermissions.hasPermissions(requireContext(), *perms)) { + try { + mSession = Session.Builder(context, apiKey, sessionId).build() + mSession!!.setSessionListener(this) + mSession!!.connect(token) + } catch (e: Exception) { + e.printStackTrace() + } + } else { + EasyPermissions.requestPermissions(this, getString(R.string.remaining_ar), RC_VIDEO_APP_PERM, *perms) + } + } + + override fun onConnected(session: Session?) { + Log.i(TAG, "Session Connected") + mPublisher = Publisher.Builder(requireContext()).build() + mPublisher!!.setPublisherListener(this) + mPublisherViewContainer!!.addView(mPublisher!!.view) + if (mPublisher!!.getView() is GLSurfaceView) { + (mPublisher!!.getView() as GLSurfaceView).setZOrderOnTop(true) + } + mSession!!.publish(mPublisher) + } + + override fun onDisconnected(session: Session) { + Log.d(TAG, "onDisconnected: disconnected from session " + session.sessionId) + mSession = null + } + + override fun onError(session: Session, opentokError: OpentokError) { + Log.d(TAG, "onError: Error (" + opentokError.message + ") in session " + session.sessionId) + Toast.makeText(requireContext(), "Session error. See the logcat please.", Toast.LENGTH_LONG).show() + requireActivity().finish() + } + + override fun onStreamReceived(session: Session, stream: Stream) { + Log.d(TAG, "onStreamReceived: New stream " + stream.streamId + " in session " + session.sessionId) + if (mSubscriber != null) { + isConnected = true + return + } + isConnected = true + subscribeToStream(stream) + videoCallPresenter.callChangeCallStatus(ChangeCallStatusRequestModel(3, sessionStatusModel!!.doctorId, sessionStatusModel!!.generalid, token, sessionStatusModel!!.vcid)) + } + + override fun onStreamDropped(session: Session, stream: Stream) { + Log.d(TAG, "onStreamDropped: Stream " + stream.streamId + " dropped from session " + session.sessionId) + if (mSubscriber == null) { + return + } + if (mSubscriber!!.stream == stream) { + mSubscriberViewContainer!!.removeView(mSubscriber!!.view) + mSubscriber!!.destroy() + mSubscriber = null + } + disconnectSession() + } + + override fun onStreamCreated(publisherKit: PublisherKit?, stream: Stream) { + Log.d(TAG, "onStreamCreated: Own stream " + stream.streamId + " created") + } + + override fun onStreamDestroyed(publisherKit: PublisherKit?, stream: Stream) { + Log.d(TAG, "onStreamDestroyed: Own stream " + stream.streamId + " destroyed") + } + + override fun onError(publisherKit: PublisherKit?, opentokError: OpentokError) { + Log.d(VideoCallFragment.TAG, "onError: Error (" + opentokError.message + ") in publisher") + Toast.makeText(requireContext(), "Session error. See the logcat please.", Toast.LENGTH_LONG).show() + requireActivity().finish() + } + + override fun onVideoDataReceived(subscriberKit: SubscriberKit?) { + mSubscriber!!.setStyle(BaseVideoRenderer.STYLE_VIDEO_SCALE, BaseVideoRenderer.STYLE_VIDEO_FILL) + mSubscriberViewContainer!!.addView(mSubscriber!!.view) + } + + override fun onVideoDisabled(subscriberKit: SubscriberKit?, s: String?) {} + + override fun onVideoEnabled(subscriberKit: SubscriberKit?, s: String?) {} + + override fun onVideoDisableWarning(subscriberKit: SubscriberKit?) {} + + override fun onVideoDisableWarningLifted(subscriberKit: SubscriberKit?) {} + + private fun subscribeToStream(stream: Stream) { + mSubscriber = Subscriber.Builder(requireContext(), stream).build() + mSubscriber!!.setVideoListener(this) + mSession!!.subscribe(mSubscriber) + } + + private fun disconnectSession() { + if (mSession == null) { + requireActivity().setResult(Activity.RESULT_CANCELED) + requireActivity().finish() + return + } + if (mSubscriber != null) { + mSubscriberViewContainer!!.removeView(mSubscriber!!.view) + mSession!!.unsubscribe(mSubscriber) + mSubscriber!!.destroy() + mSubscriber = null + } + if (mPublisher != null) { + mPublisherViewContainer!!.removeView(mPublisher!!.view) + mSession!!.unpublish(mPublisher) + mPublisher!!.destroy() + mPublisher = null + } + mSession!!.disconnect() + countDownTimer?.cancel() + videoCallPresenter.callChangeCallStatus(ChangeCallStatusRequestModel(16, sessionStatusModel!!.doctorId, sessionStatusModel!!.generalid, token, sessionStatusModel!!.vcid)) + requireActivity().finish() + } + + fun onSwitchCameraClicked(view: View?) { + if (mPublisher != null) { + isSwitchCameraClicked = !isSwitchCameraClicked + mPublisher!!.cycleCamera() + val res = if (isSwitchCameraClicked) R.drawable.flip_disapled else R.drawable.flip_enabled + mSwitchCameraBtn!!.setImageResource(res) + } + } + + fun onCameraClicked(view: View?) { + if (mPublisher != null) { + isCameraClicked = !isCameraClicked + mPublisher!!.publishVideo = !isCameraClicked + val res = if (isCameraClicked) R.drawable.video_disanabled else R.drawable.video_enabled + mCameraBtn!!.setImageResource(res) + } + } + + fun onSpeckerClicked(view: View?) { + if (mSubscriber != null) { + isSpeckerClicked = !isSpeckerClicked + mSubscriber!!.subscribeToAudio = !isSpeckerClicked + val res = if (isSpeckerClicked) R.drawable.audio_disabled else R.drawable.audio_enabled + mspeckerBtn!!.setImageResource(res) + } + } + + fun onMicClicked(view: View?) { + if (mPublisher != null) { + isMicClicked = !isMicClicked + mPublisher!!.publishAudio = !isMicClicked + val res = if (isMicClicked) R.drawable.mic_disabled else R.drawable.mic_enabled + mMicBtn!!.setImageResource(res) + } + } + + fun onCallClicked(view: View?) { + disconnectSession() + } + + override fun onCallSuccessful(sessionStatusModel: SessionStatusModel) { + if (sessionStatusModel.sessionStatus == 2 || sessionStatusModel.sessionStatus == 3) { + val returnIntent = Intent() + returnIntent.putExtra("sessionStatusNotRespond", sessionStatusModel) + requireActivity().setResult(Activity.RESULT_OK, returnIntent) + requireActivity().finish() + } + } + + override fun onCallChangeCallStatusSuccessful(sessionStatusModel: SessionStatusModel?) {} + + override fun onFailure() {} + + companion object { + @JvmStatic + fun newInstance(args: Bundle) = + VideoCallFragment().apply { + arguments = args + } + + private val TAG = VideoCallActivity::class.java.simpleName + + private const val RC_SETTINGS_SCREEN_PERM = 123 + private const val RC_VIDEO_APP_PERM = 124 + + } +} \ No newline at end of file diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 74349756..bc694f7c 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -4,5 +4,7 @@ الوقت المتبقي بالثانيه: Settings Cancel + + Hello blank fragment \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 77df9848..49ff6372 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -587,7 +587,7 @@ packages: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.3-nullsafety.1" + version: "0.6.2" json_annotation: dependency: transitive description: From 1a158f49bb7de67116f40b932619c35a5576b9d2 Mon Sep 17 00:00:00 2001 From: Zohaib Iqbal Kambrani <> Date: Wed, 9 Jun 2021 16:23:18 +0300 Subject: [PATCH 03/30] no message --- ios/Runner/Base.lproj/Main.storyboard | 21 ++++++--- ios/Runner/MainAppViewController.swift | 11 +++-- ios/Runner/VCEmbeder.swift | 30 +++++++----- ios/Runner/VideoCallViewController.swift | 58 ++++++++++-------------- 4 files changed, 64 insertions(+), 56 deletions(-) diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard index 17833007..9f1b5e1a 100755 --- a/ios/Runner/Base.lproj/Main.storyboard +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -39,6 +39,7 @@ + @@ -50,7 +51,7 @@ - + @@ -78,6 +79,11 @@ + + + + + @@ -134,6 +140,7 @@ + @@ -150,6 +157,7 @@ + @@ -160,20 +168,19 @@ + - - + + - - + + - - diff --git a/ios/Runner/MainAppViewController.swift b/ios/Runner/MainAppViewController.swift index c6466343..9a59b862 100644 --- a/ios/Runner/MainAppViewController.swift +++ b/ios/Runner/MainAppViewController.swift @@ -47,8 +47,10 @@ extension MainAppViewController : ICallProtocol{ func prepareVideoCallView(){ videoCallContainer = UIView(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height)) - videoCallContainer.alpha = 0.0 videoCallContainer.backgroundColor = UIColor.black + videoCallContainer.isHidden = true + videoCallContainer.clipsToBounds = true + view.addSubview(videoCallContainer) setVideoViewConstrints() NSLayoutConstraint.activate(vdoCallViewMaxConstraint) @@ -97,12 +99,13 @@ extension MainAppViewController : ICallProtocol{ } private func showVideoCallView(_ value:Bool){ + videoCallContainer.alpha = value ? 0.0 : 1 + self.videoCallContainer.isHidden = !value + UIView.animate(withDuration: 0.5) { self.videoCallContainer.alpha = value ? 1.0 : 0.0 } completion: { complete in - if(value == false){ - self.videoCallContainer.removeFromSuperview() - } + self.videoCallContainer.isHidden = !value } } diff --git a/ios/Runner/VCEmbeder.swift b/ios/Runner/VCEmbeder.swift index e86dde75..7e2ea0fd 100644 --- a/ios/Runner/VCEmbeder.swift +++ b/ios/Runner/VCEmbeder.swift @@ -9,25 +9,31 @@ import Foundation extension UIView { - func fill(to parent: UIView) { - topAnchor.constraint(equalTo: parent.topAnchor).isActive = true - leadingAnchor.constraint(equalTo: parent.leadingAnchor).isActive = true - bottomAnchor.constraint(equalTo: parent.bottomAnchor).isActive = true - trailingAnchor.constraint(equalTo: parent.trailingAnchor).isActive = true + func fill(to parent: UIView, animateDuration:Double = 0.5) { + self.topAnchor.constraint(equalTo: parent.topAnchor).isActive = true + self.leadingAnchor.constraint(equalTo: parent.leadingAnchor).isActive = true + self.bottomAnchor.constraint(equalTo: parent.bottomAnchor).isActive = true + self.trailingAnchor.constraint(equalTo: parent.trailingAnchor).isActive = true + UIView.animate(withDuration: animateDuration) { + parent.layoutIfNeeded() + } } - func fillToParent() { + func fillToParent(animateDuration:Double = 0.5) { if let parent = self.superview{ - topAnchor.constraint(equalTo: parent.topAnchor).isActive = true - leadingAnchor.constraint(equalTo: parent.leadingAnchor).isActive = true - bottomAnchor.constraint(equalTo: parent.bottomAnchor).isActive = true - trailingAnchor.constraint(equalTo: parent.trailingAnchor).isActive = true + self.topAnchor.constraint(equalTo: parent.topAnchor).isActive = true + self.leadingAnchor.constraint(equalTo: parent.leadingAnchor).isActive = true + self.bottomAnchor.constraint(equalTo: parent.bottomAnchor).isActive = true + self.trailingAnchor.constraint(equalTo: parent.trailingAnchor).isActive = true + UIView.animate(withDuration: animateDuration) { + parent.layoutIfNeeded() + } } } - func fillTo(view:UIView) { + func fillInTo(view:UIView) { view.addSubview(self) - fill(to: view) + fillToParent() } } diff --git a/ios/Runner/VideoCallViewController.swift b/ios/Runner/VideoCallViewController.swift index a05957c9..689c9f63 100644 --- a/ios/Runner/VideoCallViewController.swift +++ b/ios/Runner/VideoCallViewController.swift @@ -101,17 +101,19 @@ class VideoCallViewController: UIViewController { sender.isSelected = minimized onMinimize?(minimized) - self.videoMuteBtn.isHidden = minimized - self.micMuteBtn.isHidden = minimized - let min_ = minimized - UIView.animate(withDuration: 1) { + UIView.animate(withDuration: 0.5) { self.actionsHeightConstraint.constant = min_ ? 30 : 60 self.localvideoTopMargin.constant = min_ ? 20 : 40 + self.videoMuteBtn.isHidden = min_ + self.micMuteBtn.isHidden = min_ - let vdoBound = self.localVideo.bounds - self.publisher?.view?.frame = CGRect(x: 0, y: 0, width: vdoBound.size.width, height: vdoBound.size.height) + let localVdoSize = self.localVideo.bounds.size + let remoteVdoSize = self.remoteVideo.bounds.size + self.publisher?.view?.frame = CGRect(x: 0, y: 0, width: localVdoSize.width, height: localVdoSize.height) + self.subscriber?.view?.frame = CGRect(x: 0, y: 0, width: remoteVdoSize.width, height: remoteVdoSize.height) self.publisher?.view?.layoutIfNeeded() + self.subscriber?.view?.layoutIfNeeded() } } @@ -324,14 +326,8 @@ class VideoCallViewController: UIViewController { extension VideoCallViewController: OTSessionDelegate { func sessionDidConnect(_ session: OTSession) { - print("The client connected to the OpenTok session.") - - - - - timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(VideoCallViewController.updateTimer)), userInfo: nil, repeats: true) - - + print("The client connected to the OpenTok session.") + timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(VideoCallViewController.updateTimer)), userInfo: nil, repeats: true) setupPublisher() } @@ -373,27 +369,23 @@ extension VideoCallViewController: OTSessionDelegate { func session(_ session: OTSession, streamCreated stream: OTStream) { - subscriber = OTSubscriber(stream: stream, delegate: self) - guard let subscriber = subscriber else { - return - } + subscriber = OTSubscriber(stream: stream, delegate: self) + guard let subscriber = subscriber else { + return + } - var error: OTError? - session.subscribe(subscriber, error: &error) - guard error == nil else { - print(error!) - return - } + var error: OTError? + session.subscribe(subscriber, error: &error) + guard error == nil else { + print(error!) + return + } - guard let subscriberView = subscriber.view else { - return - } - subscriberView.frame = UIScreen.main.bounds - view.insertSubview(subscriberView, at: 0) - -// if nil == subscriber { -// setupSubscribe(stream) -// } + guard let subscriberView = subscriber.view else { + return + } + subscriberView.frame = CGRect(x: 0, y: 0, width: remoteVideo.bounds.width, height: remoteVideo.bounds.height) + remoteVideo.addSubview(subscriberView) } func setupSubscribe(_ stream: OTStream?) { From 42a4f9330ac5819f6f763b2c84ea9570af2dd16f Mon Sep 17 00:00:00 2001 From: Zohaib Iqbal Kambrani <> Date: Thu, 10 Jun 2021 10:12:58 +0300 Subject: [PATCH 04/30] no message --- ios/Podfile | 1 + ios/Runner.xcodeproj/project.pbxproj | 4 ++++ ios/Runner/Base.lproj/Main.storyboard | 16 +++++++++++++--- ios/Runner/Extensions.swift | 20 ++++++++++++++++++++ ios/Runner/MainAppViewController.swift | 10 ++++++++-- ios/Runner/VideoCallViewController.swift | 18 +++++++++++++----- 6 files changed, 59 insertions(+), 10 deletions(-) create mode 100644 ios/Runner/Extensions.swift diff --git a/ios/Podfile b/ios/Podfile index 62207805..c2335042 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -65,6 +65,7 @@ target 'Runner' do pod 'Flutter', :path => 'Flutter' pod 'OpenTok' pod 'Alamofire', '~> 5.2' + pod 'AADraggableView' # Plugin Pods # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 0e446318..66be39bf 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 29211E4225C172B700DD740D /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 29211E4125C172B700DD740D /* GoogleService-Info.plist */; }; 300790FA266FB14B0052174C /* VCEmbeder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 300790F9266FB14B0052174C /* VCEmbeder.swift */; }; + 300790FC26710CAB0052174C /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 300790FB26710CAB0052174C /* Extensions.swift */; }; 30F70E6C266F56FD005D8F8E /* MainAppViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30F70E6B266F56FD005D8F8E /* MainAppViewController.swift */; }; 30F70E6F266F6509005D8F8E /* VideoCallRequestParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30F70E6E266F6509005D8F8E /* VideoCallRequestParameters.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; @@ -41,6 +42,7 @@ 29211CD725C165D600DD740D /* RunnerRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerRelease.entitlements; sourceTree = ""; }; 29211E4125C172B700DD740D /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 300790F9266FB14B0052174C /* VCEmbeder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VCEmbeder.swift; sourceTree = ""; }; + 300790FB26710CAB0052174C /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; 30F70E6B266F56FD005D8F8E /* MainAppViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainAppViewController.swift; sourceTree = ""; }; 30F70E6E266F6509005D8F8E /* VideoCallRequestParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoCallRequestParameters.swift; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; @@ -78,6 +80,7 @@ isa = PBXGroup; children = ( 300790F9266FB14B0052174C /* VCEmbeder.swift */, + 300790FB26710CAB0052174C /* Extensions.swift */, ); name = helpers; sourceTree = ""; @@ -326,6 +329,7 @@ 300790FA266FB14B0052174C /* VCEmbeder.swift in Sources */, 30F70E6F266F6509005D8F8E /* VideoCallRequestParameters.swift in Sources */, 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 300790FC26710CAB0052174C /* Extensions.swift in Sources */, 9CE61EBD24AB366E008D68DD /* VideoCallViewController.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 9CE61ECD24ADBB4C008D68DD /* ICallProtocoll.swift in Sources */, diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard index 9f1b5e1a..12e173a7 100755 --- a/ios/Runner/Base.lproj/Main.storyboard +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -48,7 +48,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -98,6 +98,11 @@ + + + + + @@ -237,7 +242,7 @@ - + @@ -282,6 +287,11 @@ + + + + + + + + + - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + - + + - + - + + + + - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - + - - - - - - - - diff --git a/ios/Runner/MainAppViewController.swift b/ios/Runner/MainAppViewController.swift index 35ef4e1b..a6e4bcea 100644 --- a/ios/Runner/MainAppViewController.swift +++ b/ios/Runner/MainAppViewController.swift @@ -32,8 +32,10 @@ class MainAppViewController: FlutterViewController{ videoCallChannel.setMethodCallHandler({ (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in switch call.method { - case "openVideoCall": - self.startVideoCall(result: result, call: call) + case "openVideoCall": + self.startVideoCall(result: result, call: call) + case "showVideo": + self.showVideo() default: result(FlutterMethodNotImplemented) } @@ -69,6 +71,10 @@ extension MainAppViewController : ICallProtocol{ } } + private func showVideo(){ + videoCallContainer.isHidden = false + } + private func startVideoCall(result: @escaping FlutterResult, call:FlutterMethodCall) { videoCallFlutterResult = result @@ -78,6 +84,9 @@ extension MainAppViewController : ICallProtocol{ videoCallViewController.onMinimize = { min in self.minimizeVideoCall(min) } + videoCallViewController.onHide = { + self.videoCallContainer.isHidden = true + } videoCallViewController.onClose = videoCallClosed videoCallViewController.callBack = self videoCallViewController.start(params: VideoCallRequestParameters(dictionary: arguments)) diff --git a/ios/Runner/VideoCallViewController.swift b/ios/Runner/VideoCallViewController.swift index 69d65fb3..3e8b612c 100644 --- a/ios/Runner/VideoCallViewController.swift +++ b/ios/Runner/VideoCallViewController.swift @@ -35,23 +35,30 @@ class VideoCallViewController: UIViewController { var isUserConnect : Bool = false var onMinimize:((Bool)->Void)? = nil + var onHide:(()->Void)? = nil var onClose:(()->Void)? = nil + + @IBOutlet weak var lblRemoteUsername: UILabel! + // Bottom Actions - @IBOutlet weak var actionsHeightConstraint: NSLayoutConstraint! @IBOutlet weak var videoMuteBtn: UIButton! @IBOutlet weak var micMuteBtn: UIButton! - @IBOutlet weak var localvideoTopMargin: NSLayoutConstraint! + @IBOutlet var minimizeConstraint: [NSLayoutConstraint]! + @IBOutlet var maximisedConstraint: [NSLayoutConstraint]! + + @IBOutlet weak var hideVideoBtn: UIButton! + @IBOutlet weak var draggableBoundryDefiner: UIView! var localVideoDraggable:AADraggableView? @IBOutlet weak var controlButtons: UIView! @IBOutlet weak var remoteVideoMutedIndicator: UIImageView! @IBOutlet weak var localVideoMutedBg: UIView! + @IBOutlet weak var lblCallDuration: UILabel! @IBOutlet weak var remoteVideo: UIView! @IBOutlet weak var localVideo: UIView!{ didSet{ - localVideo.layer.borderColor = UIColor.white.cgColor localVideoDraggable = localVideo?.superview as? AADraggableView localVideoDraggable?.reposition = .edgesOnly } @@ -59,6 +66,7 @@ class VideoCallViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() + localVideoDraggable?.respectedView = draggableBoundryDefiner } @IBAction func didClickMuteButton(_ sender: UIButton) { @@ -107,12 +115,16 @@ class VideoCallViewController: UIViewController { minimized = !minimized onMinimize?(minimized) sender.isSelected = minimized + + NSLayoutConstraint.activate(minimized ? minimizeConstraint : maximisedConstraint) + NSLayoutConstraint.deactivate(minimized ? maximisedConstraint : minimizeConstraint) localVideoDraggable?.enable(!minimized) + lblRemoteUsername.isHidden = minimized + hideVideoBtn.isHidden = !minimized + let min_ = minimized UIView.animate(withDuration: 0.5) { - self.actionsHeightConstraint.constant = min_ ? 30 : 60 - self.localvideoTopMargin.constant = min_ ? 20 : 40 self.videoMuteBtn.isHidden = min_ self.micMuteBtn.isHidden = min_ @@ -125,6 +137,10 @@ class VideoCallViewController: UIViewController { } } + @IBAction func hideVideoBtnTapped(_ sender: Any) { + onHide?() + } + func start(params:VideoCallRequestParameters){ self.kApiKey = params.apiKey ?? "" @@ -352,6 +368,8 @@ extension VideoCallViewController: OTSessionDelegate { } publisher?.view!.frame = CGRect(x: 0, y: 0, width: localVideo.bounds.size.width, height: localVideo.bounds.size.height) + publisher?.view?.layer.cornerRadius = 5 + publisher?.view?.clipsToBounds = true localVideo.addSubview((publisher?.view)!) } From 1b4988e5f05a9da553ecce811844f7a351999b45 Mon Sep 17 00:00:00 2001 From: Zohaib Iqbal Kambrani <> Date: Sun, 13 Jun 2021 14:45:46 +0300 Subject: [PATCH 06/30] no message --- .../xcshareddata/xcschemes/Runner.xcscheme | 8 ++- ios/Runner/Base.lproj/Main.storyboard | 70 ++++++++++++------- ios/Runner/MainAppViewController.swift | 9 ++- ios/Runner/VideoCallViewController.swift | 29 +++++++- .../patient_profile_screen.dart | 34 ++++++--- lib/util/VideoChannel.dart | 24 ++++++- ...ent-profile-header-new-design-app-bar.dart | 24 ++++++- 7 files changed, 153 insertions(+), 45 deletions(-) diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index fb2dffc4..a28140cf 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -27,6 +27,8 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + - - + + + + - + + + + + + - + + + @@ -200,6 +207,21 @@ + @@ -209,22 +231,11 @@ - - @@ -281,6 +294,7 @@ + @@ -288,18 +302,24 @@ - + - - - - - - - - + + + + + + + + + + + + + + diff --git a/ios/Runner/MainAppViewController.swift b/ios/Runner/MainAppViewController.swift index a6e4bcea..3849635e 100644 --- a/ios/Runner/MainAppViewController.swift +++ b/ios/Runner/MainAppViewController.swift @@ -27,9 +27,10 @@ class MainAppViewController: FlutterViewController{ super.viewDidAppear(animated) } + var videoCallChannel:FlutterMethodChannel? private func initFlutterBridge(){ - let videoCallChannel = FlutterMethodChannel(name: "Dr.cloudSolution/videoCall", binaryMessenger: binaryMessenger) - videoCallChannel.setMethodCallHandler({ + videoCallChannel = FlutterMethodChannel(name: "Dr.cloudSolution/videoCall", binaryMessenger: binaryMessenger) + videoCallChannel?.setMethodCallHandler({ (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in switch call.method { case "openVideoCall": @@ -86,6 +87,10 @@ extension MainAppViewController : ICallProtocol{ } videoCallViewController.onHide = { self.videoCallContainer.isHidden = true + self.videoCallChannel?.invokeMethod("onHide", arguments: nil) + } + videoCallViewController.onVideoDuration = { duration in + self.videoCallChannel?.invokeMethod("onVideoDuration", arguments: duration) } videoCallViewController.onClose = videoCallClosed videoCallViewController.callBack = self diff --git a/ios/Runner/VideoCallViewController.swift b/ios/Runner/VideoCallViewController.swift index 3e8b612c..fd25541b 100644 --- a/ios/Runner/VideoCallViewController.swift +++ b/ios/Runner/VideoCallViewController.swift @@ -36,6 +36,7 @@ class VideoCallViewController: UIViewController { var onMinimize:((Bool)->Void)? = nil var onHide:(()->Void)? = nil + var onVideoDuration:((String)->Void)? = nil var onClose:(()->Void)? = nil @@ -44,6 +45,7 @@ class VideoCallViewController: UIViewController { // Bottom Actions @IBOutlet weak var videoMuteBtn: UIButton! @IBOutlet weak var micMuteBtn: UIButton! + @IBOutlet weak var camSwitchBtn: UIButton! @IBOutlet var minimizeConstraint: [NSLayoutConstraint]! @IBOutlet var maximisedConstraint: [NSLayoutConstraint]! @@ -109,6 +111,10 @@ class VideoCallViewController: UIViewController { callBack?.sessionDone(res:["callResponse":"CallEnd"]) sessionDisconnect() } + + @IBAction func hideVideoBtnTapped(_ sender: Any) { + onHide?() + } var minimized = false @IBAction func onMinimize(_ sender: UIButton) { @@ -122,11 +128,13 @@ class VideoCallViewController: UIViewController { lblRemoteUsername.isHidden = minimized hideVideoBtn.isHidden = !minimized + lblCallDuration.superview?.isHidden = !hideVideoBtn.isHidden let min_ = minimized UIView.animate(withDuration: 0.5) { self.videoMuteBtn.isHidden = min_ self.micMuteBtn.isHidden = min_ + self.camSwitchBtn.isHidden = min_ let localVdoSize = self.localVideo.bounds.size let remoteVdoSize = self.remoteVideo.bounds.size @@ -137,8 +145,20 @@ class VideoCallViewController: UIViewController { } } - @IBAction func hideVideoBtnTapped(_ sender: Any) { - onHide?() + var durationTimer:Timer?; + func startUpdateCallDuration(){ + var seconds = 0 + durationTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in + seconds = seconds+1 + let durationSegments = (seconds / 3600, (seconds % 3600) / 60, (seconds % 3600) % 60) + let hours = String(format: "%02d", durationSegments.0) + let mins = String(format: "%02d", durationSegments.1) + let secs = String(format: "%02d", durationSegments.2) + let durationString = "\(mins):\(secs)" + + self.lblCallDuration.text = durationString + self.onVideoDuration?(durationString) + } } func start(params:VideoCallRequestParameters){ @@ -410,8 +430,11 @@ extension VideoCallViewController: OTSessionDelegate { guard let subscriberView = subscriber.view else { return } + subscriberView.frame = CGRect(x: 0, y: 0, width: remoteVideo.bounds.width, height: remoteVideo.bounds.height) remoteVideo.addSubview(subscriberView) + + startUpdateCallDuration() } func setupSubscribe(_ stream: OTStream?) { @@ -474,7 +497,7 @@ extension VideoCallViewController: OTSubscriberDelegate { print("The subscriber failed to connect to the stream.") } @objc func hideControlButtons() { - controlButtons.isHidden = true +// controlButtons.isHidden = true } } 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 30f53a0b..f2a52411 100644 --- a/lib/screens/patients/profile/profile_screen/patient_profile_screen.dart +++ b/lib/screens/patients/profile/profile_screen/patient_profile_screen.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:doctor_app_flutter/config/config.dart'; import 'package:doctor_app_flutter/core/enum/viewstate.dart'; import 'package:doctor_app_flutter/core/viewModel/LiveCarePatientViewModel.dart'; @@ -46,10 +48,15 @@ class _PatientProfileScreenState extends State TabController _tabController; int index = 0; int _activeTab = 0; + + StreamController videoCallDurationStreamController; + Stream videoCallDurationStream = (() async*{})(); @override void initState() { _tabController = TabController(length: 2, vsync: this); super.initState(); + videoCallDurationStreamController = StreamController(); + videoCallDurationStream = videoCallDurationStreamController.stream; } @override @@ -103,16 +110,20 @@ class _PatientProfileScreenState extends State Column( children: [ PatientProfileHeaderNewDesignAppBar( - patient, arrivalType ?? '0', patientType, - isInpatient: isInpatient, - isFromLiveCare: isFromLiveCare, - height: (patient.patientStatusType != null && - patient.patientStatusType == 43) - ? 210 - : isDischargedPatient - ? 240 - : 0, - isDischargedPatient: isDischargedPatient), + patient, arrivalType ?? '0', patientType, + videoCallDurationStream: videoCallDurationStream, + onVideoDurationTap: (){ + VideoChannel.show(); + }, + isInpatient: isInpatient, + isFromLiveCare: isFromLiveCare, + height: (patient.patientStatusType != null && + patient.patientStatusType == 43) + ? 210 + : isDischargedPatient + ? 240 + : 0, + isDischargedPatient: isDischargedPatient), Container( height: !isSearchAndOut ? isDischargedPatient @@ -311,6 +322,9 @@ class _PatientProfileScreenState extends State onFailure: (String error) { DrAppToastMsg.showErrorToast(error); }, + onVideoDuration: (duration){ + videoCallDurationStreamController.sink.add(duration); + }, onCallEnd: () { var asd=""; // WidgetsBinding.instance.addPostFrameCallback((_) { diff --git a/lib/util/VideoChannel.dart b/lib/util/VideoChannel.dart index a0962010..a9c2b5e2 100644 --- a/lib/util/VideoChannel.dart +++ b/lib/util/VideoChannel.dart @@ -4,15 +4,30 @@ import 'dart:io' show Platform; import 'package:doctor_app_flutter/config/config.dart'; import 'package:doctor_app_flutter/models/livecare/session_status_model.dart'; +import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class VideoChannel{ /// channel name static const _channel = const MethodChannel("Dr.cloudSolution/videoCall"); - static openVideoCallScreen( - {kApiKey, kSessionId, kToken, callDuration, warningDuration,int vcId,String tokenID,String generalId,int doctorId, Function() onCallEnd , Function(SessionStatusModel sessionStatusModel) onCallNotRespond ,Function(String error) onFailure}) async { + static openVideoCallScreen({kApiKey, kSessionId, kToken, callDuration, warningDuration,int vcId,String tokenID, + String generalId,int doctorId, Function() onCallEnd , + Function(SessionStatusModel sessionStatusModel) onCallNotRespond ,Function(String error) onFailure, VoidCallback onHide, Function(String) onVideoDuration}) async { + + onHide = onHide ?? (){}; + onVideoDuration = onVideoDuration ?? (v){}; + var result; try { + _channel.setMethodCallHandler((call) { + if(call.method == 'onHide'){ + onHide(); + }else if(call.method == 'onVideoDuration'){ + onVideoDuration(call.arguments); + } + return true as dynamic; + }); + result = await _channel.invokeMethod( 'openVideoCall', { @@ -29,6 +44,7 @@ class VideoChannel{ ); if(result['callResponse'] == 'CallEnd') { onCallEnd(); + onVideoDuration(null); } else { SessionStatusModel sessionStatusModel = SessionStatusModel.fromJson(Platform.isIOS ?result['sessionStatus'] :json.decode(result['sessionStatus'])); @@ -40,6 +56,10 @@ class VideoChannel{ } } + + static show(){ + _channel.invokeMethod("showVideo"); + } } \ No newline at end of file diff --git a/lib/widgets/patients/profile/patient-profile-header-new-design-app-bar.dart b/lib/widgets/patients/profile/patient-profile-header-new-design-app-bar.dart index 995ac57a..69bb9758 100644 --- a/lib/widgets/patients/profile/patient-profile-header-new-design-app-bar.dart +++ b/lib/widgets/patients/profile/patient-profile-header-new-design-app-bar.dart @@ -21,8 +21,11 @@ class PatientProfileHeaderNewDesignAppBar extends StatelessWidget final bool isDischargedPatient; final bool isFromLiveCare; + final Stream videoCallDurationStream; + final VoidCallback onVideoDurationTap; + PatientProfileHeaderNewDesignAppBar( - this.patient, this.patientType, this.arrivalType, {this.height = 0.0, this.isInpatient=false, this.isDischargedPatient=false, this.isFromLiveCare = false}); + this.patient, this.patientType, this.arrivalType, {this.height = 0.0, this.isInpatient=false, this.isDischargedPatient=false, this.isFromLiveCare = false, this.videoCallDurationStream, this.onVideoDurationTap}); @override Widget build(BuildContext context) { @@ -88,6 +91,25 @@ class PatientProfileHeaderNewDesignAppBar extends StatelessWidget ), ), ), + StreamBuilder( + stream: videoCallDurationStream, + builder: (BuildContext context, AsyncSnapshot snapshot) { + if(snapshot.hasData) + return InkWell( + onTap: (){ + if(onVideoDurationTap != null) + onVideoDurationTap(); + }, + child: Container( + decoration: BoxDecoration(color: Colors.red, borderRadius: BorderRadius.circular(20)), + padding: EdgeInsets.symmetric(vertical: 2, horizontal: 10), + child: Text(snapshot.data, style: TextStyle(color: Colors.white),), + ), + ); + else + return Container(); + }, + ), ]), ), Row(children: [ From f60ff85e319e8d325b0dc9383d8523cd1a994a03 Mon Sep 17 00:00:00 2001 From: Zohaib Iqbal Kambrani <> Date: Mon, 14 Jun 2021 10:54:51 +0300 Subject: [PATCH 07/30] no message --- ios/Runner/AppDelegate.swift | 1 - .../hide_video_icon.png | Bin 175 -> 906 bytes ios/Runner/Base.lproj/Main.storyboard | 16 +++---- ios/Runner/MainAppViewController.swift | 35 ++++++---------- ios/Runner/VCEmbeder.swift | 1 + ios/Runner/VideoCallViewController.swift | 39 +++++++++--------- .../patient_profile_screen.dart | 25 ++++++++--- lib/util/VideoChannel.dart | 20 ++++----- lib/util/helpers.dart | 7 ++++ ...ent-profile-header-new-design-app-bar.dart | 7 +--- pubspec.lock | 2 +- pubspec.yaml | 2 +- 12 files changed, 79 insertions(+), 76 deletions(-) diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index dc938e36..8ebd4235 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -49,7 +49,6 @@ import OpenTok navVC.modalPresentationStyle = .fullScreen window.rootViewController?.present(navVC, animated: true, completion: nil) - } } diff --git a/ios/Runner/Assets.xcassets/hide_video_icon.imageset/hide_video_icon.png b/ios/Runner/Assets.xcassets/hide_video_icon.imageset/hide_video_icon.png index 8dea84b1de80408985f9dc89728ab042c7750a7c..40889f7710b0615cba211a52f145ae817ecad67b 100644 GIT binary patch literal 906 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-mUKs7M+SzC{oH>NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5lxRtf@J#ddWzYh$IT%O>_%)r1c48n{Iv*t)JFfcJ@hD4M^`1)8S=jZArrsOB3 z>Q&?xfOIj~R9FF-xv3?I3Kh9IdBs*0wn|_XRzNmLSYJs2tfVB{Rw=?aK*2e`C{@8s z&p^*W$&O1wLBXadCCw_x#SN+*$g@?-C@Cqh($_C9FV`zK*2^zS*Eh7ZwA42+(l;{F z1**_3uFNY*tkBIXR)!b?Gsh*hIJqdZpd>RtPXT0ZVp4u-iLH_n$Rap^xU(cP4PjGW zG1OZ?59)(t^bPe4^s#A6t;oco4I~562KE=kIvbE-R{lkqsXzyVoMmTd1GWG~4Bd9J*EaW`dB5fYK%l^v_RH}Bo5VQqYnxsq(Fnj2v`&tC3ajk z`tazr<7$5V$ORbV$(}BbAsMW1FB)<&DDbcteEnY^W`4X(Q{<#C7yH!`9z}-+21X_p z4gm!iqk}aqrn0y8bKckGm6PXg+H~}tt^CVv3-iq`@m{M&QU}%G#mXmkiG{0r - - - - - - - @@ -114,6 +107,13 @@ + + + + + + + @@ -333,7 +333,7 @@ - + diff --git a/ios/Runner/MainAppViewController.swift b/ios/Runner/MainAppViewController.swift index 3849635e..87610042 100644 --- a/ios/Runner/MainAppViewController.swift +++ b/ios/Runner/MainAppViewController.swift @@ -35,8 +35,6 @@ class MainAppViewController: FlutterViewController{ switch call.method { case "openVideoCall": self.startVideoCall(result: result, call: call) - case "showVideo": - self.showVideo() default: result(FlutterMethodNotImplemented) } @@ -72,8 +70,10 @@ extension MainAppViewController : ICallProtocol{ } } - private func showVideo(){ - videoCallContainer.isHidden = false + private func showVideo(show:Bool){ + UIView.transition(with: view, duration: 0.5, options: .transitionCrossDissolve, animations: { + self.videoCallContainer.isHidden = !show + }) } private func startVideoCall(result: @escaping FlutterResult, call:FlutterMethodCall) { @@ -82,17 +82,16 @@ extension MainAppViewController : ICallProtocol{ if let arguments = call.arguments as? NSDictionary{ showVideoCallView(true) - videoCallViewController.onMinimize = { min in - self.minimizeVideoCall(min) + videoCallViewController.onFloat = { min in + self.floatVideoCallView(min) } - videoCallViewController.onHide = { - self.videoCallContainer.isHidden = true - self.videoCallChannel?.invokeMethod("onHide", arguments: nil) + videoCallViewController.onCallConnect = { + self.videoCallChannel?.invokeMethod("onCallConnected",arguments: nil) } - videoCallViewController.onVideoDuration = { duration in - self.videoCallChannel?.invokeMethod("onVideoDuration", arguments: duration) + videoCallViewController.onCallDisconnect = { + self.showVideoCallView(false) + self.videoCallChannel?.invokeMethod("onCallDisconnected",arguments: nil) } - videoCallViewController.onClose = videoCallClosed videoCallViewController.callBack = self videoCallViewController.start(params: VideoCallRequestParameters(dictionary: arguments)) } @@ -100,7 +99,7 @@ extension MainAppViewController : ICallProtocol{ } - private func minimizeVideoCall(_ value:Bool){ + private func floatVideoCallView(_ value:Bool){ videoCallContainer.enable(value) UIView.animate(withDuration: 0.5) { @@ -119,20 +118,11 @@ extension MainAppViewController : ICallProtocol{ } private func showVideoCallView(_ value:Bool){ - videoCallContainer.alpha = value ? 0.0 : 1 - self.videoCallContainer.isHidden = !value - UIView.animate(withDuration: 0.5) { - self.videoCallContainer.alpha = value ? 1.0 : 0.0 - } completion: { complete in self.videoCallContainer.isHidden = !value } } - private func videoCallClosed(){ - showVideoCallView(false) - } - func sessionDone(res: Any) { videoCallFlutterResult?(res) } @@ -144,7 +134,6 @@ extension MainAppViewController : ICallProtocol{ func setVideoViewConstrints(){ let screen = UIScreen.main.bounds - videoCallContainer.translatesAutoresizingMaskIntoConstraints = false diff --git a/ios/Runner/VCEmbeder.swift b/ios/Runner/VCEmbeder.swift index 7e2ea0fd..9617a247 100644 --- a/ios/Runner/VCEmbeder.swift +++ b/ios/Runner/VCEmbeder.swift @@ -54,6 +54,7 @@ class ViewEmbedder { let w = container.frame.size.width; let h = container.frame.size.height; child.view.frame = CGRect(x: 0, y: 0, width: w, height: h) + child.view.backgroundColor = UIColor.black child.view.fill(to: container) } diff --git a/ios/Runner/VideoCallViewController.swift b/ios/Runner/VideoCallViewController.swift index fd25541b..27268128 100644 --- a/ios/Runner/VideoCallViewController.swift +++ b/ios/Runner/VideoCallViewController.swift @@ -34,11 +34,11 @@ class VideoCallViewController: UIViewController { var seconds = 30 var isUserConnect : Bool = false - var onMinimize:((Bool)->Void)? = nil - var onHide:(()->Void)? = nil - var onVideoDuration:((String)->Void)? = nil - var onClose:(()->Void)? = nil - + var onFloat:((Bool)->Void)? = nil + var onMinimize:(()->Void)? = nil + var onCallConnect:(()->Void)? = nil + var onCallDisconnect:(()->Void)? = nil + @IBOutlet weak var lblRemoteUsername: UILabel! @@ -61,6 +61,7 @@ class VideoCallViewController: UIViewController { @IBOutlet weak var remoteVideo: UIView! @IBOutlet weak var localVideo: UIView!{ didSet{ + localVideo.layer.borderColor = UIColor.white.cgColor localVideoDraggable = localVideo?.superview as? AADraggableView localVideoDraggable?.reposition = .edgesOnly } @@ -74,7 +75,6 @@ class VideoCallViewController: UIViewController { @IBAction func didClickMuteButton(_ sender: UIButton) { sender.isSelected = !sender.isSelected publisher!.publishAudio = !sender.isSelected - } @IBAction func didClickSpeakerButton(_ sender: UIButton) { @@ -113,24 +113,24 @@ class VideoCallViewController: UIViewController { } @IBAction func hideVideoBtnTapped(_ sender: Any) { - onHide?() + onMinimize?() } - var minimized = false + var floated = false @IBAction func onMinimize(_ sender: UIButton) { - minimized = !minimized - onMinimize?(minimized) - sender.isSelected = minimized + floated = !floated + onFloat?(floated) + sender.isSelected = floated - NSLayoutConstraint.activate(minimized ? minimizeConstraint : maximisedConstraint) - NSLayoutConstraint.deactivate(minimized ? maximisedConstraint : minimizeConstraint) - localVideoDraggable?.enable(!minimized) + NSLayoutConstraint.activate(floated ? minimizeConstraint : maximisedConstraint) + NSLayoutConstraint.deactivate(floated ? maximisedConstraint : minimizeConstraint) + localVideoDraggable?.enable(!floated) - lblRemoteUsername.isHidden = minimized - hideVideoBtn.isHidden = !minimized + lblRemoteUsername.isHidden = floated + hideVideoBtn.isHidden = !floated lblCallDuration.superview?.isHidden = !hideVideoBtn.isHidden - let min_ = minimized + let min_ = floated UIView.animate(withDuration: 0.5) { self.videoMuteBtn.isHidden = min_ self.micMuteBtn.isHidden = min_ @@ -157,7 +157,6 @@ class VideoCallViewController: UIViewController { let durationString = "\(mins):\(secs)" self.lblCallDuration.text = durationString - self.onVideoDuration?(durationString) } } @@ -277,7 +276,8 @@ class VideoCallViewController: UIViewController { dismiss(animated: true) } dismiss(animated: true) - onClose?() + onCallDisconnect?() + durationTimer?.invalidate() } func requestCameraPermissionsIfNeeded() { @@ -435,6 +435,7 @@ extension VideoCallViewController: OTSessionDelegate { remoteVideo.addSubview(subscriberView) startUpdateCallDuration() + onCallConnect?() } func setupSubscribe(_ stream: OTStream?) { 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 f2a52411..5212df0c 100644 --- a/lib/screens/patients/profile/profile_screen/patient_profile_screen.dart +++ b/lib/screens/patients/profile/profile_screen/patient_profile_screen.dart @@ -22,6 +22,7 @@ import 'package:doctor_app_flutter/widgets/shared/buttons/app_buttons_widget.dar import 'package:doctor_app_flutter/widgets/shared/loader/gif_loader_dialog_utils.dart'; import 'package:flutter/material.dart'; import 'package:hexcolor/hexcolor.dart'; +import 'package:quiver/async.dart'; import '../../../../routes.dart'; @@ -95,6 +96,23 @@ class _PatientProfileScreenState extends State _activeTab = 1; } + StreamSubscription callTimer; + callConnected(){ + callTimer = CountdownTimer(Duration(minutes: 1), Duration(seconds: 1)).listen(null) + ..onDone(() { + callTimer.cancel(); + }) + ..onData((data) { + var t = Helpers.timeFrom(duration: data.elapsed); + videoCallDurationStreamController.sink.add(t); + }); + } + + callDisconnected(){ + callTimer.cancel(); + videoCallDurationStreamController.sink.add(null); + } + @override Widget build(BuildContext context) { final screenSize = MediaQuery.of(context).size; @@ -112,9 +130,6 @@ class _PatientProfileScreenState extends State PatientProfileHeaderNewDesignAppBar( patient, arrivalType ?? '0', patientType, videoCallDurationStream: videoCallDurationStream, - onVideoDurationTap: (){ - VideoChannel.show(); - }, isInpatient: isInpatient, isFromLiveCare: isFromLiveCare, height: (patient.patientStatusType != null && @@ -322,9 +337,7 @@ class _PatientProfileScreenState extends State onFailure: (String error) { DrAppToastMsg.showErrorToast(error); }, - onVideoDuration: (duration){ - videoCallDurationStreamController.sink.add(duration); - }, + onCallConnected: callConnected, onCallEnd: () { var asd=""; // WidgetsBinding.instance.addPostFrameCallback((_) { diff --git a/lib/util/VideoChannel.dart b/lib/util/VideoChannel.dart index a9c2b5e2..94e79b7e 100644 --- a/lib/util/VideoChannel.dart +++ b/lib/util/VideoChannel.dart @@ -12,18 +12,19 @@ class VideoChannel{ static const _channel = const MethodChannel("Dr.cloudSolution/videoCall"); static openVideoCallScreen({kApiKey, kSessionId, kToken, callDuration, warningDuration,int vcId,String tokenID, String generalId,int doctorId, Function() onCallEnd , - Function(SessionStatusModel sessionStatusModel) onCallNotRespond ,Function(String error) onFailure, VoidCallback onHide, Function(String) onVideoDuration}) async { + Function(SessionStatusModel sessionStatusModel) onCallNotRespond ,Function(String error) onFailure, VoidCallback onCallConnected, VoidCallback onCallDisconnected}) async { - onHide = onHide ?? (){}; - onVideoDuration = onVideoDuration ?? (v){}; + onCallConnected = onCallConnected ?? (){}; + onCallDisconnected = onCallDisconnected ?? (){}; var result; try { _channel.setMethodCallHandler((call) { - if(call.method == 'onHide'){ - onHide(); - }else if(call.method == 'onVideoDuration'){ - onVideoDuration(call.arguments); + if(call.method == 'onCallConnected'){ + onCallConnected(); + } + if(call.method == 'onCallDisconnected'){ + onCallDisconnected(); } return true as dynamic; }); @@ -44,7 +45,6 @@ class VideoChannel{ ); if(result['callResponse'] == 'CallEnd') { onCallEnd(); - onVideoDuration(null); } else { SessionStatusModel sessionStatusModel = SessionStatusModel.fromJson(Platform.isIOS ?result['sessionStatus'] :json.decode(result['sessionStatus'])); @@ -56,10 +56,6 @@ class VideoChannel{ } } - - static show(){ - _channel.invokeMethod("showVideo"); - } } \ No newline at end of file diff --git a/lib/util/helpers.dart b/lib/util/helpers.dart index 634abd02..7232f0c9 100644 --- a/lib/util/helpers.dart +++ b/lib/util/helpers.dart @@ -270,4 +270,11 @@ class Helpers { var htmlRegex = RegExp("<(“[^”]*”|'[^’]*’|[^'”>])*>"); return htmlRegex.hasMatch(text); } + + static String timeFrom({Duration duration}) { + String twoDigits(int n) => n.toString().padLeft(2, "0"); + String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60)); + String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60)); + return "$twoDigitMinutes:$twoDigitSeconds"; + } } diff --git a/lib/widgets/patients/profile/patient-profile-header-new-design-app-bar.dart b/lib/widgets/patients/profile/patient-profile-header-new-design-app-bar.dart index 69bb9758..f2a32e63 100644 --- a/lib/widgets/patients/profile/patient-profile-header-new-design-app-bar.dart +++ b/lib/widgets/patients/profile/patient-profile-header-new-design-app-bar.dart @@ -22,10 +22,9 @@ class PatientProfileHeaderNewDesignAppBar extends StatelessWidget final bool isFromLiveCare; final Stream videoCallDurationStream; - final VoidCallback onVideoDurationTap; PatientProfileHeaderNewDesignAppBar( - this.patient, this.patientType, this.arrivalType, {this.height = 0.0, this.isInpatient=false, this.isDischargedPatient=false, this.isFromLiveCare = false, this.videoCallDurationStream, this.onVideoDurationTap}); + this.patient, this.patientType, this.arrivalType, {this.height = 0.0, this.isInpatient=false, this.isDischargedPatient=false, this.isFromLiveCare = false, this.videoCallDurationStream}); @override Widget build(BuildContext context) { @@ -94,11 +93,9 @@ class PatientProfileHeaderNewDesignAppBar extends StatelessWidget StreamBuilder( stream: videoCallDurationStream, builder: (BuildContext context, AsyncSnapshot snapshot) { - if(snapshot.hasData) + if(snapshot.hasData && snapshot.data != null) return InkWell( onTap: (){ - if(onVideoDurationTap != null) - onVideoDurationTap(); }, child: Container( decoration: BoxDecoration(color: Colors.red, borderRadius: BorderRadius.circular(20)), diff --git a/pubspec.lock b/pubspec.lock index 77df9848..cac3f27b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -813,7 +813,7 @@ packages: source: hosted version: "0.1.8" quiver: - dependency: transitive + dependency: "direct main" description: name: quiver url: "https://pub.dartlang.org" diff --git a/pubspec.yaml b/pubspec.yaml index 973e8800..1429bc3e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -90,7 +90,7 @@ dependencies: speech_to_text: path: speech_to_text - + quiver: ^2.1.5 # Html Editor Enhanced html_editor_enhanced: ^1.3.0 From 6fca263109ff61765d0660ce9341508993c08330 Mon Sep 17 00:00:00 2001 From: mosazaid Date: Mon, 14 Jun 2021 14:05:14 +0300 Subject: [PATCH 08/30] video_streaming draggable popup --- .../main/kotlin/com/hmg/hmgDr/MainActivity.kt | 8 +- .../com/hmg/hmgDr/ui/VideoCallActivity.java | 30 +- .../hmgDr/ui/fragment/VideoCallFragment.kt | 290 ++++++++++++++++-- .../main/res/layout/activity_video_call.xml | 7 +- android/app/src/main/res/values/styles.xml | 17 + 5 files changed, 297 insertions(+), 55 deletions(-) diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt index b57f5732..f013d281 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt @@ -7,7 +7,7 @@ import androidx.annotation.NonNull import com.google.gson.GsonBuilder import com.hmg.hmgDr.Model.GetSessionStatusModel import com.hmg.hmgDr.Model.SessionStatusModel -import io.flutter.embedding.android.FlutterFragment +import com.hmg.hmgDr.ui.fragment.VideoCallFragment import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodCall @@ -22,7 +22,7 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler private var call: MethodCall? = null private val LAUNCH_VIDEO: Int = 1 - private val flutterFragment: FlutterFragment? = null + lateinit var dialogFragment: VideoCallFragment override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine) @@ -78,7 +78,9 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler // startActivityForResult(intent, LAUNCH_VIDEO) val transaction = supportFragmentManager.beginTransaction() -// transaction.add() + dialogFragment = VideoCallFragment.newInstance(Bundle()) + dialogFragment.isCancelable = false + dialogFragment.show(transaction, "dialog") } diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallActivity.java b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallActivity.java index be4ebfb8..0fb061f1 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallActivity.java +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallActivity.java @@ -411,10 +411,6 @@ public class VideoCallActivity extends AppCompatActivity implements EasyPermissi finish(); } - public void onMinimizedClicked(View view) { - - } - public void onSwitchCameraClicked(View view) { if (mPublisher != null) { isSwitchCameraClicked = !isSwitchCameraClicked; @@ -424,6 +420,14 @@ public class VideoCallActivity extends AppCompatActivity implements EasyPermissi } } + public void onCallClicked(View view) { + disconnectSession(); + } + + public void onMinimizedClicked(View view) { + + } + public void onCameraClicked(View view) { if (mPublisher != null) { isCameraClicked = !isCameraClicked; @@ -433,15 +437,6 @@ public class VideoCallActivity extends AppCompatActivity implements EasyPermissi } } - public void onSpeckerClicked(View view) { - if (mSubscriber != null) { - isSpeckerClicked = !isSpeckerClicked; - mSubscriber.setSubscribeToAudio(!isSpeckerClicked); - int res = isSpeckerClicked ? R.drawable.audio_disabled : R.drawable.audio_enabled; - mspeckerBtn.setImageResource(res); - } - } - public void onMicClicked(View view) { if (mPublisher != null) { @@ -452,8 +447,13 @@ public class VideoCallActivity extends AppCompatActivity implements EasyPermissi } } - public void onCallClicked(View view) { - disconnectSession(); + public void onSpeckerClicked(View view) { + if (mSubscriber != null) { + isSpeckerClicked = !isSpeckerClicked; + mSubscriber.setSubscribeToAudio(!isSpeckerClicked); + int res = isSpeckerClicked ? R.drawable.audio_disabled : R.drawable.audio_enabled; + mspeckerBtn.setImageResource(res); + } } @Override diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt index be343b89..d5a5d5da 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt @@ -3,24 +3,26 @@ package com.hmg.hmgDr.ui.fragment import android.Manifest import android.annotation.SuppressLint import android.app.Activity +import android.app.Dialog +import android.content.Context import android.content.Intent +import android.graphics.Color +import android.graphics.Point +import android.graphics.drawable.ColorDrawable import android.opengl.GLSurfaceView import android.os.Bundle import android.os.CountDownTimer import android.os.Handler import android.os.Looper import android.util.Log -import android.view.LayoutInflater -import android.view.MotionEvent -import android.view.View -import android.view.ViewGroup +import android.view.* import android.widget.* -import androidx.fragment.app.Fragment +import androidx.annotation.Nullable +import androidx.fragment.app.DialogFragment import com.hmg.hmgDr.Model.ChangeCallStatusRequestModel import com.hmg.hmgDr.Model.GetSessionStatusModel import com.hmg.hmgDr.Model.SessionStatusModel import com.hmg.hmgDr.R -import com.hmg.hmgDr.ui.VideoCallActivity import com.hmg.hmgDr.ui.VideoCallContract.VideoCallPresenter import com.hmg.hmgDr.ui.VideoCallContract.VideoCallView import com.hmg.hmgDr.ui.VideoCallPresenterImpl @@ -31,10 +33,20 @@ import pub.devrel.easypermissions.AppSettingsDialog import pub.devrel.easypermissions.EasyPermissions import pub.devrel.easypermissions.EasyPermissions.PermissionCallbacks import java.util.* +import kotlin.math.ceil -class VideoCallFragment : Fragment(), PermissionCallbacks, Session.SessionListener, PublisherListener, +class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.SessionListener, PublisherListener, SubscriberKit.VideoListener, VideoCallView { + var isFullScreen: Boolean = true + private var x_init_cord = 0 + private var y_init_cord:Int = 0 + private var x_init_margin:Int = 0 + private var y_init_margin:Int = 0 + private val szWindow: Point = Point() + private lateinit var mWindowManager: WindowManager + private var isLeft = true + lateinit var videoCallPresenter: VideoCallPresenter private var mSession: Session? = null @@ -61,11 +73,13 @@ class VideoCallFragment : Fragment(), PermissionCallbacks, Session.SessionListen private var isSpeckerClicked = false private var isMicClicked = false - private var mCallBtn: ImageView? = null - private var mCameraBtn: ImageView? = null - private var mSwitchCameraBtn: ImageView? = null - private var mspeckerBtn: ImageView? = null - private var mMicBtn: ImageView? = null + private lateinit var videoCallContainer: LinearLayout + private lateinit var mCallBtn: ImageView + private lateinit var btnMinimize: ImageView + private lateinit var mCameraBtn: ImageView + private lateinit var mSwitchCameraBtn: ImageView + private lateinit var mspeckerBtn: ImageView + private lateinit var mMicBtn: ImageView private val progressBar: ProgressBar? = null private val countDownTimer: CountDownTimer? = null @@ -84,6 +98,45 @@ class VideoCallFragment : Fragment(), PermissionCallbacks, Session.SessionListen requestPermissions() } + override fun onStart() { + super.onStart() + + dialog?.window?.setLayout( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.MATCH_PARENT + ) + } + + override fun getTheme(): Int { + return R.style.dialogTheme + } + + override fun onCreateDialog(@Nullable savedInstanceState: Bundle?): Dialog { + val dialog: Dialog = super.onCreateDialog(savedInstanceState) + return dialog + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + // This is done in a post() since the dialog must be drawn before locating. + requireView().post { + val dialogWindow = dialog!!.window + + if (dialog != null && dialogWindow != null) { + dialogWindow.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)); + } + + // Make the dialog possible to be outside touch + dialogWindow!!.setFlags( + WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL, + WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + ) + dialogWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) + requireView().invalidate() + } + } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { @@ -91,6 +144,7 @@ class VideoCallFragment : Fragment(), PermissionCallbacks, Session.SessionListen Objects.requireNonNull(requireActivity().actionBar)!!.hide() initUI(view) + handleDragDialog() return view } @@ -121,6 +175,7 @@ class VideoCallFragment : Fragment(), PermissionCallbacks, Session.SessionListen @SuppressLint("ClickableViewAccessibility") private fun initUI(view: View) { + videoCallContainer = view.findViewById(R.id.video_call_ll) mPublisherViewContainer = view.findViewById(R.id.local_video_view_container) mSubscriberViewContainer = view.findViewById(R.id.remote_video_view_container) @@ -136,11 +191,29 @@ class VideoCallFragment : Fragment(), PermissionCallbacks, Session.SessionListen controlPanel = view.findViewById(R.id.control_panel) videoCallPresenter = VideoCallPresenterImpl(this, baseUrl) mCallBtn = view.findViewById(R.id.btn_call) + mCallBtn.setOnClickListener { + onCallClicked(it) + } + btnMinimize = view.findViewById(R.id.btn_minimize) + btnMinimize.setOnClickListener { + onMinimizedClicked(it) + } mCameraBtn = view.findViewById(R.id.btn_camera) + mCameraBtn.setOnClickListener { + onCameraClicked(it) + } mSwitchCameraBtn = view.findViewById(R.id.btn_switch_camera) + mSwitchCameraBtn.setOnClickListener { + onSwitchCameraClicked(it) + } mspeckerBtn = view.findViewById(R.id.btn_specker) + mspeckerBtn.setOnClickListener { + onSpeckerClicked(it) + } mMicBtn = view.findViewById(R.id.btn_mic) - + mMicBtn.setOnClickListener { + onMicClicked(it) + } // progressBarLayout=findViewById(R.id.progressBar); // progressBar=findViewById(R.id.progress_bar); // progressBarTextView=findViewById(R.id.progress_bar_text); @@ -316,6 +389,20 @@ class VideoCallFragment : Fragment(), PermissionCallbacks, Session.SessionListen requireActivity().finish() } + + override fun onCallSuccessful(sessionStatusModel: SessionStatusModel) { + if (sessionStatusModel.sessionStatus == 2 || sessionStatusModel.sessionStatus == 3) { + val returnIntent = Intent() + returnIntent.putExtra("sessionStatusNotRespond", sessionStatusModel) + requireActivity().setResult(Activity.RESULT_OK, returnIntent) + requireActivity().finish() + } + } + + override fun onCallChangeCallStatusSuccessful(sessionStatusModel: SessionStatusModel?) {} + + override fun onFailure() {} + fun onSwitchCameraClicked(view: View?) { if (mPublisher != null) { isSwitchCameraClicked = !isSwitchCameraClicked @@ -325,6 +412,25 @@ class VideoCallFragment : Fragment(), PermissionCallbacks, Session.SessionListen } } + fun onCallClicked(view: View?) { + disconnectSession() + } + + fun onMinimizedClicked(view: View?) { + if (isFullScreen) { + dialog?.window?.setLayout( + 400, + 600 + ) + } else { + dialog?.window?.setLayout( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.MATCH_PARENT + ) + } + isFullScreen = !isFullScreen + } + fun onCameraClicked(view: View?) { if (mPublisher != null) { isCameraClicked = !isCameraClicked @@ -334,6 +440,15 @@ class VideoCallFragment : Fragment(), PermissionCallbacks, Session.SessionListen } } + fun onMicClicked(view: View?) { + if (mPublisher != null) { + isMicClicked = !isMicClicked + mPublisher!!.publishAudio = !isMicClicked + val res = if (isMicClicked) R.drawable.mic_disabled else R.drawable.mic_enabled + mMicBtn!!.setImageResource(res) + } + } + fun onSpeckerClicked(view: View?) { if (mSubscriber != null) { isSpeckerClicked = !isSpeckerClicked @@ -343,31 +458,144 @@ class VideoCallFragment : Fragment(), PermissionCallbacks, Session.SessionListen } } - fun onMicClicked(view: View?) { - if (mPublisher != null) { - isMicClicked = !isMicClicked - mPublisher!!.publishAudio = !isMicClicked - val res = if (isMicClicked) R.drawable.mic_disabled else R.drawable.mic_enabled - mMicBtn!!.setImageResource(res) + @SuppressLint("ClickableViewAccessibility") + fun handleDragDialog(){ + mWindowManager = requireActivity().getSystemService(Context.WINDOW_SERVICE) as WindowManager + getWindowManagerDefaultDisplay() + + videoCallContainer.setOnTouchListener { _, event -> + //Get Floating widget view params + //Get Floating widget view params + val layoutParams : WindowManager.LayoutParams = dialog!!.window!!.attributes + //get the touch location coordinates + val x_cord = event.rawX.toInt() + val y_cord = event.rawY.toInt() + val x_cord_Destination: Int + var y_cord_Destination: Int + + when (event.action) { + MotionEvent.ACTION_DOWN -> { + x_init_cord = x_cord + y_init_cord = y_cord + + //remember the initial position. + x_init_margin = layoutParams.x + y_init_margin = layoutParams.y + } + MotionEvent.ACTION_UP -> { + //Get the difference between initial coordinate and current coordinate + val x_diff: Int = x_cord - x_init_cord + val y_diff: Int = y_cord - y_init_cord + + y_cord_Destination = y_init_margin + y_diff + val barHeight: Int = getStatusBarHeight() + if (y_cord_Destination < 0) { +// y_cord_Destination = 0 + y_cord_Destination = + -(szWindow.y - (videoCallContainer.height /*+ barHeight*/)) + } else if (y_cord_Destination + (videoCallContainer.height + barHeight) > szWindow.y) { + y_cord_Destination = + szWindow.y - (videoCallContainer.height + barHeight) + } + layoutParams.y = y_cord_Destination + + //reset position if user drags the floating view + resetPosition(x_cord) + } + MotionEvent.ACTION_MOVE -> { + val x_diff_move: Int = x_cord - x_init_cord + val y_diff_move: Int = y_cord - y_init_cord + x_cord_Destination = x_init_margin + x_diff_move + y_cord_Destination = y_init_margin + y_diff_move + + layoutParams.x = x_cord_Destination + layoutParams.y = y_cord_Destination + + dialog!!.window!!.attributes = layoutParams + } + } + true } } - fun onCallClicked(view: View?) { - disconnectSession() + /* Reset position of Floating Widget view on dragging */ + private fun resetPosition(x_cord_now: Int) { + if (x_cord_now <= szWindow.x / 2) { + isLeft = true + moveToLeft(x_cord_now) + } else { + isLeft = false + moveToRight(x_cord_now) + } } - override fun onCallSuccessful(sessionStatusModel: SessionStatusModel) { - if (sessionStatusModel.sessionStatus == 2 || sessionStatusModel.sessionStatus == 3) { - val returnIntent = Intent() - returnIntent.putExtra("sessionStatusNotRespond", sessionStatusModel) - requireActivity().setResult(Activity.RESULT_OK, returnIntent) - requireActivity().finish() - } + /* Method to move the Floating widget view to Left */ + private fun moveToLeft(current_x_cord: Int) { + + var mParams : WindowManager.LayoutParams = dialog!!.window!!.attributes + + mParams.x = + (szWindow.x - current_x_cord * current_x_cord - videoCallContainer.width).toInt() + + dialog!!.window!!.attributes = mParams + val x = szWindow.x - current_x_cord + object : CountDownTimer(500, 5) { + //get params of Floating Widget view + var mParams : WindowManager.LayoutParams = dialog!!.window!!.attributes + override fun onTick(t: Long) { + val step = (500 - t) / 5 + // mParams.x = 0 - (current_x_cord * current_x_cord * step).toInt() + mParams.x = + (szWindow.x - current_x_cord * current_x_cord * step - videoCallContainer.width).toInt() + + dialog!!.window!!.attributes = mParams + } + + override fun onFinish() { + mParams.x = -(szWindow.x - videoCallContainer.width) + + dialog!!.window!!.attributes = mParams + } + }.start() } - override fun onCallChangeCallStatusSuccessful(sessionStatusModel: SessionStatusModel?) {} + /* Method to move the Floating widget view to Right */ + private fun moveToRight(current_x_cord: Int) { +// var mParams : WindowManager.LayoutParams = dialog!!.window!!.attributes +// mParams.x = +// (szWindow.x + current_x_cord * current_x_cord - videoCallContainer.width).toInt() +// +// dialog!!.window!!.attributes = mParams + object : CountDownTimer(500, 5) { + //get params of Floating Widget view + var mParams : WindowManager.LayoutParams = dialog!!.window!!.attributes + override fun onTick(t: Long) { + val step = (500 - t) / 5 + mParams.x = + (szWindow.x + current_x_cord * current_x_cord * step - videoCallContainer.width).toInt() + + dialog!!.window!!.attributes = mParams + } - override fun onFailure() {} + override fun onFinish() { + mParams.x = szWindow.x - videoCallContainer.width + + dialog!!.window!!.attributes = mParams + } + }.start() + } + + private fun getWindowManagerDefaultDisplay() { + mWindowManager.getDefaultDisplay() + .getSize(szWindow) + } + + /* return status bar height on basis of device display metrics */ + private fun getStatusBarHeight(): Int { + return ceil( + (25 * requireActivity().applicationContext.resources.displayMetrics.density).toDouble() + ).toInt() + } companion object { @JvmStatic @@ -376,7 +604,7 @@ class VideoCallFragment : Fragment(), PermissionCallbacks, Session.SessionListen arguments = args } - private val TAG = VideoCallActivity::class.java.simpleName + private val TAG = VideoCallFragment::class.java.simpleName private const val RC_SETTINGS_SCREEN_PERM = 123 private const val RC_VIDEO_APP_PERM = 124 diff --git a/android/app/src/main/res/layout/activity_video_call.xml b/android/app/src/main/res/layout/activity_video_call.xml index a3c2ced1..b7ca8d8d 100644 --- a/android/app/src/main/res/layout/activity_video_call.xml +++ b/android/app/src/main/res/layout/activity_video_call.xml @@ -1,6 +1,7 @@ @@ -132,7 +132,6 @@ android:layout_width="@dimen/video_icon_size" android:layout_height="@dimen/video_icon_size" android:layout_alignParentEnd="true" - android:onClick="onCallClicked" android:scaleType="centerCrop" android:src="@drawable/call" /> @@ -141,7 +140,6 @@ android:layout_width="@dimen/video_icon_size" android:layout_height="@dimen/video_icon_size" android:layout_alignParentStart="true" - android:onClick="onMinimizedClicked" android:layout_marginEnd="@dimen/padding_space_small" android:scaleType="centerCrop" android:src="@drawable/ic_mini" /> @@ -151,7 +149,6 @@ android:layout_width="@dimen/video_icon_size" android:layout_height="@dimen/video_icon_size" android:layout_toEndOf="@id/btn_minimize" - android:onClick="onCameraClicked" android:scaleType="centerCrop" android:layout_marginEnd="@dimen/padding_space_small" android:src="@drawable/video_enabled" /> @@ -161,7 +158,6 @@ android:layout_width="@dimen/video_icon_size" android:layout_height="@dimen/video_icon_size" android:layout_toEndOf="@id/btn_camera" - android:onClick="onMicClicked" android:layout_marginEnd="@dimen/padding_space_small" android:scaleType="centerCrop" android:src="@drawable/mic_enabled" /> @@ -171,7 +167,6 @@ android:layout_width="@dimen/video_icon_size" android:layout_height="@dimen/video_icon_size" android:layout_toEndOf="@id/btn_mic" - android:onClick="onSpeckerClicked" android:scaleType="centerCrop" android:layout_marginEnd="@dimen/padding_space_small" android:src="@drawable/audio_enabled" /> diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index 24355fae..e13b5b14 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -16,4 +16,21 @@ true @null + + + From 48874c0efdca0100d79638ae031fce14c5953c20 Mon Sep 17 00:00:00 2001 From: Zohaib Iqbal Kambrani <> Date: Mon, 14 Jun 2021 17:27:21 +0300 Subject: [PATCH 09/30] no message --- ios/Runner/Base.lproj/Main.storyboard | 172 ++++++++++++----------- ios/Runner/MainAppViewController.swift | 78 +++++++--- ios/Runner/VideoCallViewController.swift | 59 ++++---- 3 files changed, 183 insertions(+), 126 deletions(-) diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard index cecbd930..c1c16274 100755 --- a/ios/Runner/Base.lproj/Main.storyboard +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -39,79 +39,18 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + - - + @@ -128,7 +67,7 @@ - + @@ -261,7 +201,7 @@ - + @@ -270,11 +210,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -285,7 +293,10 @@ + + + @@ -294,17 +305,19 @@ + - + + @@ -333,7 +346,6 @@ - diff --git a/ios/Runner/MainAppViewController.swift b/ios/Runner/MainAppViewController.swift index 87610042..ffca444c 100644 --- a/ios/Runner/MainAppViewController.swift +++ b/ios/Runner/MainAppViewController.swift @@ -13,9 +13,10 @@ class MainAppViewController: FlutterViewController{ var videoCallContainer:AADraggableView! var videoCallViewController:VideoCallViewController! var videoCallFlutterResult:FlutterResult? - var vdoCallViewMinConstraint:[NSLayoutConstraint]! - var vdoCallViewMaxConstraint:[NSLayoutConstraint]! - + var vdoCallViewFloatRectConstraint:[NSLayoutConstraint]! + var vdoCallViewFullConstraint:[NSLayoutConstraint]! + var vdoCallViewFloatCircleConstraint:[NSLayoutConstraint]! + override func viewDidLoad() { super.viewDidLoad() @@ -58,15 +59,15 @@ extension MainAppViewController : ICallProtocol{ view.addSubview(videoCallContainer) setVideoViewConstrints() - NSLayoutConstraint.activate(vdoCallViewMaxConstraint) - NSLayoutConstraint.deactivate(vdoCallViewMinConstraint) + NSLayoutConstraint.activate(vdoCallViewFullConstraint) + NSLayoutConstraint.deactivate(vdoCallViewFloatRectConstraint) ViewEmbedder.embed( withIdentifier: "videoCall", // Storyboard ID parent: self, container: self.videoCallContainer){ vc in self.videoCallViewController = vc as? VideoCallViewController - + } } @@ -82,8 +83,17 @@ extension MainAppViewController : ICallProtocol{ if let arguments = call.arguments as? NSDictionary{ showVideoCallView(true) - videoCallViewController.onFloat = { min in - self.floatVideoCallView(min) + videoCallViewController.onRectFloat = { min in + self.rectFloatVideoCallView(min) + if(min){ + self.videoCallContainer.repositionIfNeeded() + } + } + + videoCallViewController.onCircleFloat = { min in + self.circleFloatVideoCallView(min) + self.videoCallContainer.reposition = min ? .free : .edgesOnly + self.videoCallContainer.repositionIfNeeded() } videoCallViewController.onCallConnect = { self.videoCallChannel?.invokeMethod("onCallConnected",arguments: nil) @@ -99,16 +109,18 @@ extension MainAppViewController : ICallProtocol{ } - private func floatVideoCallView(_ value:Bool){ + private func rectFloatVideoCallView(_ value:Bool){ videoCallContainer.enable(value) UIView.animate(withDuration: 0.5) { if(value){ - NSLayoutConstraint.deactivate(self.vdoCallViewMaxConstraint) - NSLayoutConstraint.activate(self.vdoCallViewMinConstraint) + NSLayoutConstraint.deactivate(self.vdoCallViewFullConstraint) + NSLayoutConstraint.deactivate(self.vdoCallViewFloatCircleConstraint) + NSLayoutConstraint.activate(self.vdoCallViewFloatRectConstraint) }else{ - NSLayoutConstraint.deactivate(self.vdoCallViewMinConstraint) - NSLayoutConstraint.activate(self.vdoCallViewMaxConstraint) + NSLayoutConstraint.deactivate(self.vdoCallViewFloatRectConstraint) + NSLayoutConstraint.deactivate(self.vdoCallViewFloatCircleConstraint) + NSLayoutConstraint.activate(self.vdoCallViewFullConstraint) } self.videoCallContainer.layer.cornerRadius = value ? 10 : 0 self.videoCallContainer.layer.borderColor = value ? UIColor.white.cgColor : nil @@ -117,6 +129,28 @@ extension MainAppViewController : ICallProtocol{ } } + private func circleFloatVideoCallView(_ value:Bool){ + videoCallContainer.enable(value) + + UIView.animate(withDuration: 0.5) { + if(value){ + NSLayoutConstraint.deactivate(self.vdoCallViewFullConstraint) + NSLayoutConstraint.deactivate(self.vdoCallViewFloatRectConstraint) + NSLayoutConstraint.activate(self.vdoCallViewFloatCircleConstraint) + self.videoCallContainer.layer.cornerRadius = 35 + }else{ + NSLayoutConstraint.activate(self.vdoCallViewFloatRectConstraint) + NSLayoutConstraint.deactivate(self.vdoCallViewFullConstraint) + NSLayoutConstraint.deactivate(self.vdoCallViewFloatCircleConstraint) + self.videoCallContainer.layer.cornerRadius = 10 + + } + self.videoCallContainer.layer.borderColor = value ? UIColor.white.cgColor : nil + self.videoCallContainer.layer.borderWidth = value ? 2 : 0 + self.view.layoutIfNeeded() + } + } + private func showVideoCallView(_ value:Bool){ UIView.animate(withDuration: 0.5) { self.videoCallContainer.isHidden = !value @@ -133,21 +167,31 @@ extension MainAppViewController : ICallProtocol{ func setVideoViewConstrints(){ + videoCallContainer.layer.shadowColor = UIColor.black.cgColor + videoCallContainer.layer.shadowOffset = CGSize(width: 1, height: 1) + videoCallContainer.layer.shadowRadius = 5 + let screen = UIScreen.main.bounds videoCallContainer.translatesAutoresizingMaskIntoConstraints = false - vdoCallViewMinConstraint = [ + vdoCallViewFullConstraint = [ + videoCallContainer.topAnchor.constraint(equalTo: view.topAnchor), + videoCallContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor), + videoCallContainer.widthAnchor.constraint(equalToConstant: screen.width), + videoCallContainer.heightAnchor.constraint(equalToConstant: screen.height) + ] + vdoCallViewFloatRectConstraint = [ videoCallContainer.topAnchor.constraint(equalTo: view.topAnchor, constant: 20), videoCallContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), videoCallContainer.widthAnchor.constraint(equalToConstant: screen.width/3), videoCallContainer.heightAnchor.constraint(equalToConstant: screen.height/3.5) ] - vdoCallViewMaxConstraint = [ + vdoCallViewFloatCircleConstraint = [ videoCallContainer.topAnchor.constraint(equalTo: view.topAnchor), videoCallContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor), - videoCallContainer.widthAnchor.constraint(equalToConstant: screen.width), - videoCallContainer.heightAnchor.constraint(equalToConstant: screen.height) + videoCallContainer.widthAnchor.constraint(equalToConstant: 70), + videoCallContainer.heightAnchor.constraint(equalToConstant: 70) ] } diff --git a/ios/Runner/VideoCallViewController.swift b/ios/Runner/VideoCallViewController.swift index 27268128..02a70a0c 100644 --- a/ios/Runner/VideoCallViewController.swift +++ b/ios/Runner/VideoCallViewController.swift @@ -34,8 +34,8 @@ class VideoCallViewController: UIViewController { var seconds = 30 var isUserConnect : Bool = false - var onFloat:((Bool)->Void)? = nil - var onMinimize:(()->Void)? = nil + var onRectFloat:((Bool)->Void)? = nil + var onCircleFloat:((Bool)->Void)? = nil var onCallConnect:(()->Void)? = nil var onCallDisconnect:(()->Void)? = nil @@ -50,13 +50,15 @@ class VideoCallViewController: UIViewController { @IBOutlet var minimizeConstraint: [NSLayoutConstraint]! @IBOutlet var maximisedConstraint: [NSLayoutConstraint]! + @IBOutlet weak var btnMinimize: UIButton! @IBOutlet weak var hideVideoBtn: UIButton! - @IBOutlet weak var draggableBoundryDefiner: UIView! var localVideoDraggable:AADraggableView? @IBOutlet weak var controlButtons: UIView! @IBOutlet weak var remoteVideoMutedIndicator: UIImageView! @IBOutlet weak var localVideoMutedBg: UIView! + @IBOutlet weak var localVideoContainer: UIView! + @IBOutlet weak var topBar: UIView! @IBOutlet weak var lblCallDuration: UILabel! @IBOutlet weak var remoteVideo: UIView! @IBOutlet weak var localVideo: UIView!{ @@ -69,18 +71,31 @@ class VideoCallViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - localVideoDraggable?.respectedView = draggableBoundryDefiner + localVideoDraggable?.respectedView = localVideoContainer } + @objc func click(gesture:UIGestureRecognizer){ + gesture.view?.removeFromSuperview() + } + + @IBAction func onVideoContainerTapped(_ sender: Any) { + if(hideVideoBtn.isSelected){ + circleFloatBtnTapped(hideVideoBtn) + + }else if(btnMinimize.isSelected){ + btnMinimizeTapped(btnMinimize) + }else if(!btnMinimize.isSelected){ + // Swipe video here + } + } @IBAction func didClickMuteButton(_ sender: UIButton) { sender.isSelected = !sender.isSelected publisher!.publishAudio = !sender.isSelected } - @IBAction func didClickSpeakerButton(_ sender: UIButton) { + @IBAction func didClickSpeakerButton(_ sender: UIButton) { sender.isSelected = !sender.isSelected subscriber?.subscribeToAudio = !sender.isSelected - // resetHideButtonsTimer() } @IBAction func didClickVideoMuteButton(_ sender: UIButton) { @@ -92,7 +107,6 @@ class VideoCallViewController: UIViewController { } localVideo.isHidden = sender.isSelected localVideoMutedBg.isHidden = !sender.isSelected - // resetHideButtonsTimer() } @@ -104,7 +118,6 @@ class VideoCallViewController: UIViewController { } else { publisher!.cameraPosition = AVCaptureDevice.Position.back } - /// resetHideButtonsTimer() } @IBAction func hangUp(_ sender: UIButton) { @@ -112,14 +125,19 @@ class VideoCallViewController: UIViewController { sessionDisconnect() } - @IBAction func hideVideoBtnTapped(_ sender: Any) { - onMinimize?() + @IBAction func circleFloatBtnTapped(_ sender: UIButton) { + sender.isSelected = !sender.isSelected + onCircleFloat?(sender.isSelected) + topBar.isHidden = sender.isSelected + controlButtons.isHidden = sender.isSelected + localVideo.isHidden = sender.isSelected + self.publisher?.view?.layoutIfNeeded() } var floated = false - @IBAction func onMinimize(_ sender: UIButton) { + @IBAction func btnMinimizeTapped(_ sender: UIButton) { floated = !floated - onFloat?(floated) + onRectFloat?(floated) sender.isSelected = floated NSLayoutConstraint.activate(floated ? minimizeConstraint : maximisedConstraint) @@ -171,7 +189,6 @@ class VideoCallViewController: UIViewController { self.DoctorId = params.doctorId ?? 0 self.baseUrl = params.baseUrl ?? "" - setupButtons() askForMicrophonePermission() requestCameraPermissionsIfNeeded() hideVideoMuted() @@ -238,13 +255,6 @@ class VideoCallViewController: UIViewController { } } - - func setupButtons() { - perform(#selector(hideControlButtons), with: nil, afterDelay: 3) - let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(remoteVideoTapped(_:))) - view.addGestureRecognizer(tapGestureRecognizer) - view.isUserInteractionEnabled = true - } // MARK: -Microphone Camera and Permission Request func askForMicrophonePermission() { @@ -481,12 +491,6 @@ extension VideoCallViewController: OTPublisherDelegate { func publisher(_ publisher: OTPublisherKit, didFailWithError error: OTError) { print("The publisher failed: \(error)") } - @objc func remoteVideoTapped(_ recognizer: UITapGestureRecognizer?) { - if controlButtons.isHidden { - controlButtons.isHidden = false - perform(#selector(hideControlButtons), with: nil, afterDelay: 3) - } - } } extension VideoCallViewController: OTSubscriberDelegate { @@ -497,8 +501,5 @@ extension VideoCallViewController: OTSubscriberDelegate { public func subscriber(_ subscriber: OTSubscriberKit, didFailWithError error: OTError) { print("The subscriber failed to connect to the stream.") } - @objc func hideControlButtons() { -// controlButtons.isHidden = true - } } From 883778d27813700ff6aaa82ffaf5bb95f813ab2e Mon Sep 17 00:00:00 2001 From: mosazaid Date: Tue, 15 Jun 2021 12:17:24 +0300 Subject: [PATCH 10/30] video call --- .../main/kotlin/com/hmg/hmgDr/MainActivity.kt | 41 ++- .../hmg/hmgDr/ui/VideoCallResponseListener.kt | 8 + .../hmgDr/ui/fragment/VideoCallFragment.kt | 310 +++++++++++++----- .../main/res/drawable/layout_rounded_bg.xml | 7 + .../main/res/layout/activity_video_call.xml | 96 +++--- android/app/src/main/res/values/dimens.xml | 8 +- .../viewModel/authentication_view_model.dart | 4 +- .../patient_profile_screen.dart | 1 + ...ent-profile-header-new-design-app-bar.dart | 21 +- 9 files changed, 359 insertions(+), 137 deletions(-) create mode 100644 android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallResponseListener.kt create mode 100644 android/app/src/main/res/drawable/layout_rounded_bg.xml diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt index f013d281..d595be97 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt @@ -7,6 +7,7 @@ import androidx.annotation.NonNull import com.google.gson.GsonBuilder import com.hmg.hmgDr.Model.GetSessionStatusModel import com.hmg.hmgDr.Model.SessionStatusModel +import com.hmg.hmgDr.ui.VideoCallResponseListener import com.hmg.hmgDr.ui.fragment.VideoCallFragment import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.engine.FlutterEngine @@ -15,7 +16,7 @@ import io.flutter.plugin.common.MethodChannel import io.flutter.plugins.GeneratedPluginRegistrant -class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler { +class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, VideoCallResponseListener { private val CHANNEL = "Dr.cloudSolution/videoCall" private var result: MethodChannel.Result? = null @@ -67,7 +68,6 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler private fun openVideoCall(apiKey: String?, sessionId: String?, token: String?, appLang: String?, baseUrl: String?, sessionStatusModel: GetSessionStatusModel) { // val videoCallActivity = VideoCallActivity() - // val intent = Intent(this, VideoCallActivity::class.java) // intent.putExtra("apiKey", apiKey) // intent.putExtra("sessionId", sessionId) @@ -77,14 +77,23 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler // intent.putExtra("sessionStatusModel", sessionStatusModel) // startActivityForResult(intent, LAUNCH_VIDEO) + val arguments = Bundle() + arguments.putString("apiKey", apiKey) + arguments.putString("sessionId", sessionId) + arguments.putString("token", token) + arguments.putString("appLang", appLang) + arguments.putString("baseUrl", baseUrl) + arguments.putParcelable("sessionStatusModel", sessionStatusModel) + val transaction = supportFragmentManager.beginTransaction() - dialogFragment = VideoCallFragment.newInstance(Bundle()) - dialogFragment.isCancelable = false + dialogFragment = VideoCallFragment.newInstance(arguments) + dialogFragment.setCallListener(this) + dialogFragment.isCancelable = true dialogFragment.show(transaction, "dialog") } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + /* override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) var asd = ""; if (requestCode == LAUNCH_VIDEO) { @@ -108,6 +117,28 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler result?.success(callResponse) } } + }*/ + + override fun onCallFinished(resultCode: Int, intent: Intent?) { + if (resultCode == Activity.RESULT_OK) { + val result : SessionStatusModel? = intent?.getParcelableExtra("sessionStatusNotRespond") + val callResponse : HashMap = HashMap() + + val sessionStatus : HashMap = HashMap() + val gson = GsonBuilder().serializeNulls().create() + + callResponse["callResponse"] = "CallNotRespond" + val jsonRes = gson.toJson(result) + callResponse["sessionStatus"] = jsonRes + + this.result?.success(callResponse) + } + if (resultCode == Activity.RESULT_CANCELED) { + val callResponse : HashMap = HashMap() + callResponse["callResponse"] = "CallEnd" + + result?.success(callResponse) + } } diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallResponseListener.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallResponseListener.kt new file mode 100644 index 00000000..e32630b4 --- /dev/null +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallResponseListener.kt @@ -0,0 +1,8 @@ +package com.hmg.hmgDr.ui + +import android.content.Intent + +interface VideoCallResponseListener { + + fun onCallFinished(resultCode : Int, intent: Intent? = null) +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt index d5a5d5da..dd4613bd 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt @@ -10,39 +10,38 @@ import android.graphics.Color import android.graphics.Point import android.graphics.drawable.ColorDrawable import android.opengl.GLSurfaceView -import android.os.Bundle -import android.os.CountDownTimer -import android.os.Handler -import android.os.Looper +import android.os.* import android.util.Log import android.view.* import android.widget.* import androidx.annotation.Nullable +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet import androidx.fragment.app.DialogFragment -import com.hmg.hmgDr.Model.ChangeCallStatusRequestModel import com.hmg.hmgDr.Model.GetSessionStatusModel import com.hmg.hmgDr.Model.SessionStatusModel import com.hmg.hmgDr.R import com.hmg.hmgDr.ui.VideoCallContract.VideoCallPresenter import com.hmg.hmgDr.ui.VideoCallContract.VideoCallView import com.hmg.hmgDr.ui.VideoCallPresenterImpl +import com.hmg.hmgDr.ui.VideoCallResponseListener import com.opentok.android.* import com.opentok.android.PublisherKit.PublisherListener import pub.devrel.easypermissions.AfterPermissionGranted import pub.devrel.easypermissions.AppSettingsDialog import pub.devrel.easypermissions.EasyPermissions import pub.devrel.easypermissions.EasyPermissions.PermissionCallbacks -import java.util.* import kotlin.math.ceil + class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.SessionListener, PublisherListener, SubscriberKit.VideoListener, VideoCallView { var isFullScreen: Boolean = true private var x_init_cord = 0 - private var y_init_cord:Int = 0 - private var x_init_margin:Int = 0 - private var y_init_margin:Int = 0 + private var y_init_cord: Int = 0 + private var x_init_margin: Int = 0 + private var y_init_margin: Int = 0 private val szWindow: Point = Point() private lateinit var mWindowManager: WindowManager private var isLeft = true @@ -58,9 +57,11 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session private var mVolRunnable: Runnable? = null private var mConnectedRunnable: Runnable? = null - private var mPublisherViewContainer: FrameLayout? = null - private var mSubscriberViewContainer: RelativeLayout? = null - private var controlPanel: RelativeLayout? = null + private lateinit var mPublisherViewContainer: FrameLayout + private lateinit var mPublisherViewIcon: View + private lateinit var mSubscriberViewContainer: FrameLayout + private lateinit var mSubscriberViewIcon: ImageView + private var controlPanel: ConstraintLayout? = null private var apiKey: String? = null private var sessionId: String? = null @@ -73,7 +74,9 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session private var isSpeckerClicked = false private var isMicClicked = false - private lateinit var videoCallContainer: LinearLayout + private lateinit var parentView: View + private lateinit var videoCallContainer: ConstraintLayout + private lateinit var layoutName: RelativeLayout private lateinit var mCallBtn: ImageView private lateinit var btnMinimize: ImageView private lateinit var mCameraBtn: ImageView @@ -81,6 +84,11 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session private lateinit var mspeckerBtn: ImageView private lateinit var mMicBtn: ImageView + private lateinit var patientName: TextView + private lateinit var cmTimer: Chronometer + var elapsedTime: Long = 0 + var resume = false + private val progressBar: ProgressBar? = null private val countDownTimer: CountDownTimer? = null private val progressBarTextView: TextView? = null @@ -89,6 +97,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session private var isConnected = false private var sessionStatusModel: GetSessionStatusModel? = null + private var videoCallResponseListener: VideoCallResponseListener? =null override fun onCreate(savedInstanceState: Bundle?) { @@ -137,16 +146,20 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session } } + fun setCallListener(videoCallResponseListener: VideoCallResponseListener){ + this.videoCallResponseListener = videoCallResponseListener + } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - val view = inflater.inflate(R.layout.activity_video_call, container, false) + parentView = inflater.inflate(R.layout.activity_video_call, container, false) - Objects.requireNonNull(requireActivity().actionBar)!!.hide() - initUI(view) +// Objects.requireNonNull(requireActivity().actionBar)!!.hide() + initUI(parentView) handleDragDialog() - return view + return parentView } override fun onPause() { @@ -170,14 +183,18 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session override fun onDestroy() { disconnectSession() + cmTimer.stop() super.onDestroy() } @SuppressLint("ClickableViewAccessibility") private fun initUI(view: View) { - videoCallContainer = view.findViewById(R.id.video_call_ll) - mPublisherViewContainer = view.findViewById(R.id.local_video_view_container) - mSubscriberViewContainer = view.findViewById(R.id.remote_video_view_container) + videoCallContainer = view.findViewById(R.id.video_call_ll) + layoutName = view.findViewById(R.id.layout_name) + mPublisherViewContainer = view.findViewById(R.id.local_video_view_container) + mPublisherViewIcon = view.findViewById(R.id.local_video_view_icon) + mSubscriberViewIcon = view.findViewById(R.id.remote_video_view_icon) + mSubscriberViewContainer = view.findViewById(R.id.remote_video_view_container) arguments?.run { apiKey = getString("apiKey") @@ -188,7 +205,27 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session sessionStatusModel = getParcelable("sessionStatusModel") } - controlPanel = view.findViewById(R.id.control_panel) + patientName = view.findViewById(R.id.patient_name) + patientName.text = sessionStatusModel!!.patientName + + cmTimer = view.findViewById(R.id.cmTimer) + cmTimer.format = "mm:ss" + cmTimer.onChronometerTickListener = Chronometer.OnChronometerTickListener { arg0: Chronometer? -> + val minutes: Long + val seconds: Long + if (!resume) { + minutes = (SystemClock.elapsedRealtime() - cmTimer.base) / 1000 / 60 + seconds = (SystemClock.elapsedRealtime() - cmTimer.base) / 1000 % 60 + elapsedTime = SystemClock.elapsedRealtime() + } else { + minutes = (elapsedTime - cmTimer.base) / 1000 / 60 + seconds = (elapsedTime - cmTimer.base) / 1000 % 60 + elapsedTime = elapsedTime + 1000 + } + Log.d(VideoCallFragment.TAG, "onChronometerTick: $minutes : $seconds") + } + + controlPanel = view.findViewById(R.id.control_panel) videoCallPresenter = VideoCallPresenterImpl(this, baseUrl) mCallBtn = view.findViewById(R.id.btn_call) mCallBtn.setOnClickListener { @@ -295,17 +332,23 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session (mPublisher!!.getView() as GLSurfaceView).setZOrderOnTop(true) } mSession!!.publish(mPublisher) + + if (!resume) { + cmTimer.base = SystemClock.elapsedRealtime() + } + cmTimer.start() } override fun onDisconnected(session: Session) { Log.d(TAG, "onDisconnected: disconnected from session " + session.sessionId) mSession = null + cmTimer.stop() } override fun onError(session: Session, opentokError: OpentokError) { Log.d(TAG, "onError: Error (" + opentokError.message + ") in session " + session.sessionId) - Toast.makeText(requireContext(), "Session error. See the logcat please.", Toast.LENGTH_LONG).show() - requireActivity().finish() +// Toast.makeText(requireContext(), "Session error. See the logcat please.", Toast.LENGTH_LONG).show() +// requireActivity().finish() } override fun onStreamReceived(session: Session, stream: Stream) { @@ -316,7 +359,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session } isConnected = true subscribeToStream(stream) - videoCallPresenter.callChangeCallStatus(ChangeCallStatusRequestModel(3, sessionStatusModel!!.doctorId, sessionStatusModel!!.generalid, token, sessionStatusModel!!.vcid)) +// videoCallPresenter.callChangeCallStatus(ChangeCallStatusRequestModel(3, sessionStatusModel!!.doctorId, sessionStatusModel!!.generalid, token, sessionStatusModel!!.vcid)) } override fun onStreamDropped(session: Session, stream: Stream) { @@ -342,8 +385,8 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session override fun onError(publisherKit: PublisherKit?, opentokError: OpentokError) { Log.d(VideoCallFragment.TAG, "onError: Error (" + opentokError.message + ") in publisher") - Toast.makeText(requireContext(), "Session error. See the logcat please.", Toast.LENGTH_LONG).show() - requireActivity().finish() +// Toast.makeText(requireContext(), "Session error. See the logcat please.", Toast.LENGTH_LONG).show() +// requireActivity().finish() } override fun onVideoDataReceived(subscriberKit: SubscriberKit?) { @@ -367,8 +410,9 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session private fun disconnectSession() { if (mSession == null) { - requireActivity().setResult(Activity.RESULT_CANCELED) - requireActivity().finish() + videoCallResponseListener?.onCallFinished(Activity.RESULT_CANCELED) +// requireActivity().setResult(Activity.RESULT_CANCELED) + dialog?.dismiss() return } if (mSubscriber != null) { @@ -385,8 +429,8 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session } mSession!!.disconnect() countDownTimer?.cancel() - videoCallPresenter.callChangeCallStatus(ChangeCallStatusRequestModel(16, sessionStatusModel!!.doctorId, sessionStatusModel!!.generalid, token, sessionStatusModel!!.vcid)) - requireActivity().finish() +// videoCallPresenter.callChangeCallStatus(ChangeCallStatusRequestModel(16, sessionStatusModel!!.doctorId, sessionStatusModel!!.generalid, token, sessionStatusModel!!.vcid)) + dialog?.dismiss() } @@ -394,8 +438,9 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session if (sessionStatusModel.sessionStatus == 2 || sessionStatusModel.sessionStatus == 3) { val returnIntent = Intent() returnIntent.putExtra("sessionStatusNotRespond", sessionStatusModel) - requireActivity().setResult(Activity.RESULT_OK, returnIntent) - requireActivity().finish() + videoCallResponseListener?.onCallFinished(Activity.RESULT_OK, returnIntent) +// requireActivity().setResult(Activity.RESULT_OK, returnIntent) + dialog?.dismiss() } } @@ -403,7 +448,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session override fun onFailure() {} - fun onSwitchCameraClicked(view: View?) { + private fun onSwitchCameraClicked(view: View?) { if (mPublisher != null) { isSwitchCameraClicked = !isSwitchCameraClicked mPublisher!!.cycleCamera() @@ -412,11 +457,11 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session } } - fun onCallClicked(view: View?) { + private fun onCallClicked(view: View?) { disconnectSession() } - fun onMinimizedClicked(view: View?) { + private fun onMinimizedClicked(view: View?) { if (isFullScreen) { dialog?.window?.setLayout( 400, @@ -429,9 +474,89 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session ) } isFullScreen = !isFullScreen + setViewsVisibility() + } + + private fun setViewsVisibility() { + initUI(parentView) + val iconSize : Int = context!!.resources.getDimension(R.dimen.video_icon_size).toInt() + val iconSizeSmall : Int = context!!.resources.getDimension(R.dimen.video_icon_size_small).toInt() + val btnMinimizeLayoutParam : ConstraintLayout.LayoutParams = btnMinimize.layoutParams as ConstraintLayout.LayoutParams + val mCallBtnLayoutParam : ConstraintLayout.LayoutParams = mCallBtn.layoutParams as ConstraintLayout.LayoutParams + +// val localPreviewMargin : Int = context!!.resources.getDimension(R.dimen.local_preview_margin_top).toInt() +// val localPreviewWidth : Int = context!!.resources.getDimension(R.dimen.local_preview_width).toInt() +// val localPreviewHeight : Int = context!!.resources.getDimension(R.dimen.local_preview_height).toInt() +// val localPreviewIconSize: Int = context!!.resources.getDimension(R.dimen.local_back_icon_size).toInt() +// val localPreviewMarginSmall : Int = context!!.resources.getDimension(R.dimen.local_preview_margin_small).toInt() +// val localPreviewWidthSmall : Int = context!!.resources.getDimension(R.dimen.local_preview_width_small).toInt() +// val localPreviewHeightSmall : Int = context!!.resources.getDimension(R.dimen.local_preview_height_small).toInt() +// val localPreviewIconSmall: Int = context!!.resources.getDimension(R.dimen.local_back_icon_size_small).toInt() +// val localPreviewLayoutIconParam : FrameLayout.LayoutParams +// val localPreviewLayoutParam : RelativeLayout.LayoutParams = mPublisherViewContainer.layoutParams as RelativeLayout.LayoutParams + + val remotePreviewIconSize: Int = context!!.resources.getDimension(R.dimen.remote_back_icon_size).toInt() + val remotePreviewIconSizeSmall: Int = context!!.resources.getDimension(R.dimen.remote_back_icon_size_small).toInt() + val remotePreviewLayoutParam : FrameLayout.LayoutParams = mSubscriberViewIcon.layoutParams as FrameLayout.LayoutParams + + val constraintSet = ConstraintSet() + //layoutParam.constrain +// constraintSet. + if (isFullScreen) { + layoutName.visibility = View.VISIBLE + mCameraBtn.visibility = View.VISIBLE + mSwitchCameraBtn.visibility = View.VISIBLE + mspeckerBtn.visibility = View.VISIBLE + mMicBtn.visibility = View.VISIBLE + mPublisherViewContainer.visibility = View.VISIBLE + +// layoutParam = ConstraintLayout.LayoutParams(iconSize, iconSize) + btnMinimizeLayoutParam.width = iconSize + btnMinimizeLayoutParam.height = iconSize + mCallBtnLayoutParam.width = iconSize + mCallBtnLayoutParam.height = iconSize +// localPreviewLayoutIconParam = FrameLayout.LayoutParams(localPreviewIconSize, localPreviewIconSize) +//// localPreviewLayoutParam = RelativeLayout.LayoutParams(localPreviewWidth, localPreviewHeight) +// localPreviewLayoutParam.width = localPreviewIconSize +// localPreviewLayoutParam.height = localPreviewIconSize +// localPreviewLayoutParam.setMargins(0,localPreviewMargin, localPreviewMargin, 0) +// remotePreviewLayoutParam = FrameLayout.LayoutParams(remotePreviewIconSize, remotePreviewIconSize) + remotePreviewLayoutParam.width = remotePreviewIconSize + remotePreviewLayoutParam.height = remotePreviewIconSize + } else { + layoutName.visibility = View.GONE + mCameraBtn.visibility = View.GONE + mSwitchCameraBtn.visibility = View.GONE + mspeckerBtn.visibility = View.GONE + mMicBtn.visibility = View.GONE + mPublisherViewContainer.visibility = View.GONE + +// layoutParam = ConstraintLayout.LayoutParams(iconSizeSmall, iconSizeSmall) + btnMinimizeLayoutParam.width = iconSizeSmall + btnMinimizeLayoutParam.height = iconSizeSmall + mCallBtnLayoutParam.width = iconSizeSmall + mCallBtnLayoutParam.height = iconSizeSmall + +// localPreviewLayoutIconParam = FrameLayout.LayoutParams(localPreviewIconSmall, localPreviewIconSmall) +//// localPreviewLayoutParam = RelativeLayout.LayoutParams(localPreviewWidthSmall, localPreviewHeightSmall) +// localPreviewLayoutParam.width = localPreviewWidthSmall +// localPreviewLayoutParam.height = localPreviewWidthSmall +// localPreviewLayoutParam.setMargins(0,localPreviewMarginSmall, localPreviewMarginSmall, 0) +// remotePreviewLayoutParam = FrameLayout.LayoutParams(remotePreviewIconSizeSmall, remotePreviewIconSizeSmall) + remotePreviewLayoutParam.width = remotePreviewIconSizeSmall + remotePreviewLayoutParam.height = remotePreviewIconSizeSmall + } + +// mPublisherViewContainer.layoutParams = localPreviewLayoutParam +// mPublisherViewIcon.layoutParams = localPreviewLayoutIconParam + mSubscriberViewIcon.layoutParams = remotePreviewLayoutParam + + btnMinimize.layoutParams = btnMinimizeLayoutParam + mCallBtn.layoutParams = mCallBtnLayoutParam + } - fun onCameraClicked(view: View?) { + private fun onCameraClicked(view: View?) { if (mPublisher != null) { isCameraClicked = !isCameraClicked mPublisher!!.publishVideo = !isCameraClicked @@ -440,7 +565,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session } } - fun onMicClicked(view: View?) { + private fun onMicClicked(view: View?) { if (mPublisher != null) { isMicClicked = !isMicClicked mPublisher!!.publishAudio = !isMicClicked @@ -449,7 +574,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session } } - fun onSpeckerClicked(view: View?) { + private fun onSpeckerClicked(view: View?) { if (mSubscriber != null) { isSpeckerClicked = !isSpeckerClicked mSubscriber!!.subscribeToAudio = !isSpeckerClicked @@ -458,64 +583,69 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session } } + @SuppressLint("ClickableViewAccessibility") - fun handleDragDialog(){ + fun handleDragDialog() { mWindowManager = requireActivity().getSystemService(Context.WINDOW_SERVICE) as WindowManager getWindowManagerDefaultDisplay() - videoCallContainer.setOnTouchListener { _, event -> - //Get Floating widget view params - //Get Floating widget view params - val layoutParams : WindowManager.LayoutParams = dialog!!.window!!.attributes - //get the touch location coordinates - val x_cord = event.rawX.toInt() - val y_cord = event.rawY.toInt() - val x_cord_Destination: Int - var y_cord_Destination: Int - - when (event.action) { - MotionEvent.ACTION_DOWN -> { - x_init_cord = x_cord - y_init_cord = y_cord - - //remember the initial position. - x_init_margin = layoutParams.x - y_init_margin = layoutParams.y - } - MotionEvent.ACTION_UP -> { - //Get the difference between initial coordinate and current coordinate - val x_diff: Int = x_cord - x_init_cord - val y_diff: Int = y_cord - y_init_cord - - y_cord_Destination = y_init_margin + y_diff - val barHeight: Int = getStatusBarHeight() - if (y_cord_Destination < 0) { + videoCallContainer.setOnTouchListener(dragListener) +// mSubscriberViewContainer.setOnTouchListener(dragListener) + } + + @SuppressLint("ClickableViewAccessibility") + val dragListener : View.OnTouchListener = View.OnTouchListener{ _, event -> + //Get Floating widget view params + //Get Floating widget view params + val layoutParams: WindowManager.LayoutParams = dialog!!.window!!.attributes + //get the touch location coordinates + val x_cord = event.rawX.toInt() + val y_cord = event.rawY.toInt() + val x_cord_Destination: Int + var y_cord_Destination: Int + + when (event.action) { + MotionEvent.ACTION_DOWN -> { + x_init_cord = x_cord + y_init_cord = y_cord + + //remember the initial position. + x_init_margin = layoutParams.x + y_init_margin = layoutParams.y + } + MotionEvent.ACTION_UP -> { + //Get the difference between initial coordinate and current coordinate + val x_diff: Int = x_cord - x_init_cord + val y_diff: Int = y_cord - y_init_cord + + y_cord_Destination = y_init_margin + y_diff + val barHeight: Int = getStatusBarHeight() + if (y_cord_Destination < 0) { // y_cord_Destination = 0 - y_cord_Destination = - -(szWindow.y - (videoCallContainer.height /*+ barHeight*/)) - } else if (y_cord_Destination + (videoCallContainer.height + barHeight) > szWindow.y) { - y_cord_Destination = - szWindow.y - (videoCallContainer.height + barHeight) - } - layoutParams.y = y_cord_Destination - - //reset position if user drags the floating view - resetPosition(x_cord) + y_cord_Destination = + -(szWindow.y - (videoCallContainer.height /*+ barHeight*/)) + } else if (y_cord_Destination + (videoCallContainer.height + barHeight) > szWindow.y) { + y_cord_Destination = + szWindow.y - (videoCallContainer.height + barHeight) } - MotionEvent.ACTION_MOVE -> { - val x_diff_move: Int = x_cord - x_init_cord - val y_diff_move: Int = y_cord - y_init_cord - x_cord_Destination = x_init_margin + x_diff_move - y_cord_Destination = y_init_margin + y_diff_move + layoutParams.y = y_cord_Destination + + //reset position if user drags the floating view + resetPosition(x_cord) + } + MotionEvent.ACTION_MOVE -> { + val x_diff_move: Int = x_cord - x_init_cord + val y_diff_move: Int = y_cord - y_init_cord + x_cord_Destination = x_init_margin + x_diff_move + y_cord_Destination = y_init_margin + y_diff_move - layoutParams.x = x_cord_Destination - layoutParams.y = y_cord_Destination + layoutParams.x = x_cord_Destination + layoutParams.y = y_cord_Destination - dialog!!.window!!.attributes = layoutParams - } + dialog!!.window!!.attributes = layoutParams } - true } + true } /* Reset position of Floating Widget view on dragging */ @@ -532,16 +662,16 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session /* Method to move the Floating widget view to Left */ private fun moveToLeft(current_x_cord: Int) { - var mParams : WindowManager.LayoutParams = dialog!!.window!!.attributes + var mParams: WindowManager.LayoutParams = dialog!!.window!!.attributes mParams.x = - (szWindow.x - current_x_cord * current_x_cord - videoCallContainer.width).toInt() + (szWindow.x - current_x_cord * current_x_cord - videoCallContainer.width).toInt() dialog!!.window!!.attributes = mParams val x = szWindow.x - current_x_cord object : CountDownTimer(500, 5) { //get params of Floating Widget view - var mParams : WindowManager.LayoutParams = dialog!!.window!!.attributes + var mParams: WindowManager.LayoutParams = dialog!!.window!!.attributes override fun onTick(t: Long) { val step = (500 - t) / 5 // mParams.x = 0 - (current_x_cord * current_x_cord * step).toInt() @@ -568,7 +698,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session // dialog!!.window!!.attributes = mParams object : CountDownTimer(500, 5) { //get params of Floating Widget view - var mParams : WindowManager.LayoutParams = dialog!!.window!!.attributes + var mParams: WindowManager.LayoutParams = dialog!!.window!!.attributes override fun onTick(t: Long) { val step = (500 - t) / 5 mParams.x = diff --git a/android/app/src/main/res/drawable/layout_rounded_bg.xml b/android/app/src/main/res/drawable/layout_rounded_bg.xml new file mode 100644 index 00000000..0c46f49b --- /dev/null +++ b/android/app/src/main/res/drawable/layout_rounded_bg.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/android/app/src/main/res/layout/activity_video_call.xml b/android/app/src/main/res/layout/activity_video_call.xml index b7ca8d8d..0b3baff7 100644 --- a/android/app/src/main/res/layout/activity_video_call.xml +++ b/android/app/src/main/res/layout/activity_video_call.xml @@ -1,5 +1,6 @@ - + android:padding="@dimen/padding_space_medium" + app:layout_constraintTop_toTopOf="parent"> + tools:text="25:45" /> @@ -47,7 +50,8 @@ android:id="@+id/activity_clingo_video_call" android:layout_width="match_parent" android:layout_height="0dp" - android:layout_weight="1" + app:layout_constraintBottom_toTopOf="@id/control_panel" + app:layout_constraintTop_toBottomOf="@+id/layout_name" tools:context=".ui.VideoCallActivity"> - - + - + - + android:padding="@dimen/padding_space_big" + app:layout_constraintBottom_toBottomOf="parent"> + android:src="@drawable/call" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + android:src="@drawable/ic_mini" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + android:scaleType="centerCrop" + android:src="@drawable/video_enabled" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toEndOf="@id/btn_minimize" + app:layout_constraintTop_toTopOf="parent" + /> + android:src="@drawable/mic_enabled" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toEndOf="@id/btn_camera" + app:layout_constraintTop_toTopOf="parent" + /> - - - + android:scaleType="centerCrop" + android:src="@drawable/audio_enabled" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toEndOf="@id/btn_mic" + app:layout_constraintTop_toTopOf="parent" + /> + + + diff --git a/android/app/src/main/res/values/dimens.xml b/android/app/src/main/res/values/dimens.xml index 2d53d554..2a4d695f 100644 --- a/android/app/src/main/res/values/dimens.xml +++ b/android/app/src/main/res/values/dimens.xml @@ -3,21 +3,27 @@ 16dp 16dp 28dp + 12dp 24dp 60dp 54dp - 64dp + 52dp + 24dp 24dp 25dp 88dp + 40dp 117dp + 50dp 50dp + 25dp 100dp + 40dp 90dp diff --git a/lib/core/viewModel/authentication_view_model.dart b/lib/core/viewModel/authentication_view_model.dart index 58df9331..189dea42 100644 --- a/lib/core/viewModel/authentication_view_model.dart +++ b/lib/core/viewModel/authentication_view_model.dart @@ -254,8 +254,8 @@ class AuthenticationViewModel extends BaseViewModel { /// add  token to shared preferences in case of send activation code is success setDataAfterSendActivationSuccess(SendActivationCodeForDoctorAppResponseModel sendActivationCodeForDoctorAppResponseModel) { - print("VerificationCode : " + - sendActivationCodeForDoctorAppResponseModel.verificationCode); + print("VerificationCode : " + sendActivationCodeForDoctorAppResponseModel.verificationCode); + DrAppToastMsg.showSuccesToast("VerificationCode : " + sendActivationCodeForDoctorAppResponseModel.verificationCode); sharedPref.setString(VIDA_AUTH_TOKEN_ID, sendActivationCodeForDoctorAppResponseModel.vidaAuthTokenID); sharedPref.setString(VIDA_REFRESH_TOKEN_ID, 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 e0053b81..d755af4a 100644 --- a/lib/screens/patients/profile/profile_screen/patient_profile_screen.dart +++ b/lib/screens/patients/profile/profile_screen/patient_profile_screen.dart @@ -17,6 +17,7 @@ import 'package:doctor_app_flutter/util/dr_app_toast_msg.dart'; import 'package:doctor_app_flutter/util/helpers.dart'; import 'package:doctor_app_flutter/util/translations_delegate_base.dart'; import 'package:doctor_app_flutter/widgets/patients/profile/patient-profile-app-bar.dart'; +import 'package:doctor_app_flutter/widgets/patients/profile/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/buttons/app_buttons_widget.dart'; import 'package:doctor_app_flutter/widgets/shared/loader/gif_loader_dialog_utils.dart'; diff --git a/lib/widgets/patients/profile/patient-profile-header-new-design-app-bar.dart b/lib/widgets/patients/profile/patient-profile-header-new-design-app-bar.dart index 995ac57a..f2a32e63 100644 --- a/lib/widgets/patients/profile/patient-profile-header-new-design-app-bar.dart +++ b/lib/widgets/patients/profile/patient-profile-header-new-design-app-bar.dart @@ -21,8 +21,10 @@ class PatientProfileHeaderNewDesignAppBar extends StatelessWidget final bool isDischargedPatient; final bool isFromLiveCare; + final Stream videoCallDurationStream; + PatientProfileHeaderNewDesignAppBar( - this.patient, this.patientType, this.arrivalType, {this.height = 0.0, this.isInpatient=false, this.isDischargedPatient=false, this.isFromLiveCare = false}); + this.patient, this.patientType, this.arrivalType, {this.height = 0.0, this.isInpatient=false, this.isDischargedPatient=false, this.isFromLiveCare = false, this.videoCallDurationStream}); @override Widget build(BuildContext context) { @@ -88,6 +90,23 @@ class PatientProfileHeaderNewDesignAppBar extends StatelessWidget ), ), ), + StreamBuilder( + stream: videoCallDurationStream, + builder: (BuildContext context, AsyncSnapshot snapshot) { + if(snapshot.hasData && snapshot.data != null) + return InkWell( + onTap: (){ + }, + child: Container( + decoration: BoxDecoration(color: Colors.red, borderRadius: BorderRadius.circular(20)), + padding: EdgeInsets.symmetric(vertical: 2, horizontal: 10), + child: Text(snapshot.data, style: TextStyle(color: Colors.white),), + ), + ); + else + return Container(); + }, + ), ]), ), Row(children: [ From 890a356d8c4afe3e931d0a6ed77ca19c79b8b1a1 Mon Sep 17 00:00:00 2001 From: mosazaid Date: Tue, 15 Jun 2021 15:52:28 +0300 Subject: [PATCH 11/30] video fix bugs --- .../main/kotlin/com/hmg/hmgDr/MainActivity.kt | 42 +++++++++------ .../hmg/hmgDr/ui/VideoCallResponseListener.kt | 2 + .../hmgDr/ui/fragment/VideoCallFragment.kt | 53 +++++++++++-------- .../main/res/layout/activity_video_call.xml | 40 +++----------- 4 files changed, 66 insertions(+), 71 deletions(-) diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt index d595be97..d3c58371 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt @@ -3,6 +3,7 @@ package com.hmg.hmgDr import android.app.Activity import android.content.Intent import android.os.Bundle +import android.widget.Toast import androidx.annotation.NonNull import com.google.gson.GsonBuilder import com.hmg.hmgDr.Model.GetSessionStatusModel @@ -23,7 +24,7 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, private var call: MethodCall? = null private val LAUNCH_VIDEO: Int = 1 - lateinit var dialogFragment: VideoCallFragment + private var dialogFragment: VideoCallFragment? = null override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine) @@ -77,19 +78,23 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, // intent.putExtra("sessionStatusModel", sessionStatusModel) // startActivityForResult(intent, LAUNCH_VIDEO) - val arguments = Bundle() - arguments.putString("apiKey", apiKey) - arguments.putString("sessionId", sessionId) - arguments.putString("token", token) - arguments.putString("appLang", appLang) - arguments.putString("baseUrl", baseUrl) - arguments.putParcelable("sessionStatusModel", sessionStatusModel) - - val transaction = supportFragmentManager.beginTransaction() - dialogFragment = VideoCallFragment.newInstance(arguments) - dialogFragment.setCallListener(this) - dialogFragment.isCancelable = true - dialogFragment.show(transaction, "dialog") + if (dialogFragment == null){ + val arguments = Bundle() + arguments.putString("apiKey", apiKey) + arguments.putString("sessionId", sessionId) + arguments.putString("token", token) + arguments.putString("appLang", appLang) + arguments.putString("baseUrl", baseUrl) + arguments.putParcelable("sessionStatusModel", sessionStatusModel) + + val transaction = supportFragmentManager.beginTransaction() + dialogFragment = VideoCallFragment.newInstance(arguments) + dialogFragment?.let { + it.setCallListener(this) + it.isCancelable = true + it.show(transaction, "dialog") + } + } } @@ -120,6 +125,8 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, }*/ override fun onCallFinished(resultCode: Int, intent: Intent?) { + dialogFragment = null + if (resultCode == Activity.RESULT_OK) { val result : SessionStatusModel? = intent?.getParcelableExtra("sessionStatusNotRespond") val callResponse : HashMap = HashMap() @@ -132,8 +139,7 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, callResponse["sessionStatus"] = jsonRes this.result?.success(callResponse) - } - if (resultCode == Activity.RESULT_CANCELED) { + } else if (resultCode == Activity.RESULT_CANCELED) { val callResponse : HashMap = HashMap() callResponse["callResponse"] = "CallEnd" @@ -141,5 +147,9 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, } } + override fun errorHandle(message: String) { + Toast.makeText(this, message, Toast.LENGTH_LONG).show() + } + } diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallResponseListener.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallResponseListener.kt index e32630b4..d2ed15e0 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallResponseListener.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallResponseListener.kt @@ -5,4 +5,6 @@ import android.content.Intent interface VideoCallResponseListener { fun onCallFinished(resultCode : Int, intent: Intent? = null) + + fun errorHandle(message: String) } \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt index dd4613bd..6c07f462 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt @@ -103,8 +103,6 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session override fun onCreate(savedInstanceState: Bundle?) { requireActivity().setTheme(R.style.AppTheme) super.onCreate(savedInstanceState) - - requestPermissions() } override fun onStart() { @@ -155,8 +153,18 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session parentView = inflater.inflate(R.layout.activity_video_call, container, false) -// Objects.requireNonNull(requireActivity().actionBar)!!.hide() + // Objects.requireNonNull(requireActivity().actionBar)!!.hide() + arguments?.run { + apiKey = getString("apiKey") + sessionId = getString("sessionId") + token = getString("token") + appLang = getString("appLang") + baseUrl = getString("baseUrl") + sessionStatusModel = getParcelable("sessionStatusModel") + } initUI(parentView) + requestPermissions() + handleDragDialog() return parentView @@ -196,15 +204,6 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session mSubscriberViewIcon = view.findViewById(R.id.remote_video_view_icon) mSubscriberViewContainer = view.findViewById(R.id.remote_video_view_container) - arguments?.run { - apiKey = getString("apiKey") - sessionId = getString("sessionId") - token = getString("token") - appLang = getString("appLang") - baseUrl = getString("baseUrl") - sessionStatusModel = getParcelable("sessionStatusModel") - } - patientName = view.findViewById(R.id.patient_name) patientName.text = sessionStatusModel!!.patientName @@ -220,8 +219,9 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session } else { minutes = (elapsedTime - cmTimer.base) / 1000 / 60 seconds = (elapsedTime - cmTimer.base) / 1000 % 60 - elapsedTime = elapsedTime + 1000 + elapsedTime += 1000 } + arg0?.text = "$minutes:$seconds" Log.d(VideoCallFragment.TAG, "onChronometerTick: $minutes : $seconds") } @@ -257,12 +257,19 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session // progressBar.setVisibility(View.GONE); hiddenButtons() checkClientConnected() - mSubscriberViewContainer!!.setOnTouchListener { v: View?, event: MotionEvent? -> + + mSubscriberViewContainer.setOnClickListener { controlPanel!!.visibility = View.VISIBLE mVolHandler!!.removeCallbacks(mVolRunnable!!) mVolHandler!!.postDelayed(mVolRunnable!!, (5 * 1000).toLong()) - true } + +// mSubscriberViewContainer!!.setOnTouchListener { v: View?, event: MotionEvent? -> +// controlPanel!!.visibility = View.VISIBLE +// mVolHandler!!.removeCallbacks(mVolRunnable!!) +// mVolHandler!!.postDelayed(mVolRunnable!!, (5 * 1000).toLong()) +// true +// } if (appLang == "ar") { progressBarLayout!!.layoutDirection = View.LAYOUT_DIRECTION_RTL } @@ -347,8 +354,9 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session override fun onError(session: Session, opentokError: OpentokError) { Log.d(TAG, "onError: Error (" + opentokError.message + ") in session " + session.sessionId) -// Toast.makeText(requireContext(), "Session error. See the logcat please.", Toast.LENGTH_LONG).show() -// requireActivity().finish() + + videoCallResponseListener?.errorHandle("onError: Error (" + opentokError.message + ") in session ") + dialog?.dismiss() } override fun onStreamReceived(session: Session, stream: Stream) { @@ -385,8 +393,8 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session override fun onError(publisherKit: PublisherKit?, opentokError: OpentokError) { Log.d(VideoCallFragment.TAG, "onError: Error (" + opentokError.message + ") in publisher") -// Toast.makeText(requireContext(), "Session error. See the logcat please.", Toast.LENGTH_LONG).show() -// requireActivity().finish() + videoCallResponseListener?.errorHandle("onError: Error (" + opentokError.message + ") in publisher") + dialog?.dismiss() } override fun onVideoDataReceived(subscriberKit: SubscriberKit?) { @@ -408,6 +416,11 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session mSession!!.subscribe(mSubscriber) } + override fun dismiss() { + videoCallResponseListener?.onCallFinished(1000) + super.dismiss() + } + private fun disconnectSession() { if (mSession == null) { videoCallResponseListener?.onCallFinished(Activity.RESULT_CANCELED) @@ -478,7 +491,6 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session } private fun setViewsVisibility() { - initUI(parentView) val iconSize : Int = context!!.resources.getDimension(R.dimen.video_icon_size).toInt() val iconSizeSmall : Int = context!!.resources.getDimension(R.dimen.video_icon_size_small).toInt() val btnMinimizeLayoutParam : ConstraintLayout.LayoutParams = btnMinimize.layoutParams as ConstraintLayout.LayoutParams @@ -553,7 +565,6 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session btnMinimize.layoutParams = btnMinimizeLayoutParam mCallBtn.layoutParams = mCallBtnLayoutParam - } private fun onCameraClicked(view: View?) { diff --git a/android/app/src/main/res/layout/activity_video_call.xml b/android/app/src/main/res/layout/activity_video_call.xml index 0b3baff7..586f5b60 100644 --- a/android/app/src/main/res/layout/activity_video_call.xml +++ b/android/app/src/main/res/layout/activity_video_call.xml @@ -54,30 +54,6 @@ app:layout_constraintTop_toBottomOf="@+id/layout_name" tools:context=".ui.VideoCallActivity"> - - - - - - + app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toTopOf="parent" /> From bc661a6f38ada1f1470fc175cf334a578a67b4f0 Mon Sep 17 00:00:00 2001 From: mosazaid Date: Tue, 15 Jun 2021 16:56:51 +0300 Subject: [PATCH 12/30] fix bugs --- .../main/kotlin/com/hmg/hmgDr/MainActivity.kt | 47 +++++++++++------- .../com/hmg/hmgDr/ui/VideoCallActivity.java | 2 +- .../hmgDr/ui/fragment/VideoCallFragment.kt | 23 +++++---- android/app/src/main/res/drawable/call.png | Bin 13229 -> 21324 bytes .../app/src/main/res/drawable/camera_back.png | Bin 0 -> 6409 bytes .../src/main/res/drawable/camera_front.png | Bin 0 -> 7100 bytes android/app/src/main/res/drawable/expand.png | Bin 0 -> 5366 bytes .../src/main/res/drawable/flip_disapled.png | Bin 6138 -> 0 bytes .../src/main/res/drawable/flip_enabled.png | Bin 6753 -> 0 bytes android/app/src/main/res/drawable/ic_mini.xml | 16 ------ .../src/main/res/drawable/mic_disabled.png | Bin 5783 -> 6537 bytes .../app/src/main/res/drawable/mic_enabled.png | Bin 6020 -> 5904 bytes .../app/src/main/res/drawable/reducing.png | Bin 0 -> 4953 bytes .../main/res/drawable/video_disanabled.png | Bin 5113 -> 6438 bytes .../src/main/res/drawable/video_enabled.png | Bin 5338 -> 5715 bytes .../main/res/layout/activity_video_call.xml | 29 +++++------ android/app/src/main/res/values/dimens.xml | 2 +- 17 files changed, 60 insertions(+), 59 deletions(-) create mode 100644 android/app/src/main/res/drawable/camera_back.png create mode 100644 android/app/src/main/res/drawable/camera_front.png create mode 100644 android/app/src/main/res/drawable/expand.png delete mode 100644 android/app/src/main/res/drawable/flip_disapled.png delete mode 100644 android/app/src/main/res/drawable/flip_enabled.png delete mode 100644 android/app/src/main/res/drawable/ic_mini.xml create mode 100644 android/app/src/main/res/drawable/reducing.png diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt index d3c58371..9d9581fe 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt @@ -43,27 +43,36 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, this.result = result this.call = call - if (call.method == "openVideoCall") { - val apiKey = call.argument("kApiKey") - val sessionId = call.argument("kSessionId") - val token = call.argument("kToken") - val appLang = call.argument("appLang") - val baseUrl = call.argument("baseUrl") + when (call.method) { + "openVideoCall" -> { + val apiKey = call.argument("kApiKey") + val sessionId = call.argument("kSessionId") + val token = call.argument("kToken") + val appLang = call.argument("appLang") + val baseUrl = call.argument("baseUrl") - // Session Status model - val VC_ID = call.argument("VC_ID") - val tokenID = call.argument("TokenID") - val generalId = call.argument("generalId") - val doctorId = call.argument("DoctorId") - val patientName = call.argument("patientName") + // Session Status model + val VC_ID = call.argument("VC_ID") + val tokenID = call.argument("TokenID") + val generalId = call.argument("generalId") + val doctorId = call.argument("DoctorId") + val patientName = call.argument("patientName") - val sessionStatusModel = GetSessionStatusModel(VC_ID, tokenID, generalId, doctorId, patientName) + val sessionStatusModel = GetSessionStatusModel(VC_ID, tokenID, generalId, doctorId, patientName) - openVideoCall(apiKey, sessionId, token, appLang, baseUrl, sessionStatusModel) + openVideoCall(apiKey, sessionId, token, appLang, baseUrl, sessionStatusModel) - } else { - result.notImplemented() + } + "closeVideoCall" -> { + dialogFragment?.onCallClicked() + } + "onCallConnected"->{ + + } + else -> { + result.notImplemented() + } } } @@ -94,6 +103,9 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, it.isCancelable = true it.show(transaction, "dialog") } + } else if (!dialogFragment!!.isVisible){ + val transaction = supportFragmentManager.beginTransaction() + dialogFragment!!.show(transaction, "dialog") } } @@ -148,7 +160,8 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, } override fun errorHandle(message: String) { - Toast.makeText(this, message, Toast.LENGTH_LONG).show() + dialogFragment = null +// Toast.makeText(this, message, Toast.LENGTH_LONG).show() } diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallActivity.java b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallActivity.java index 0fb061f1..daf372ed 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallActivity.java +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallActivity.java @@ -415,7 +415,7 @@ public class VideoCallActivity extends AppCompatActivity implements EasyPermissi if (mPublisher != null) { isSwitchCameraClicked = !isSwitchCameraClicked; mPublisher.cycleCamera(); - int res = isSwitchCameraClicked ? R.drawable.flip_disapled : R.drawable.flip_enabled; + int res = isSwitchCameraClicked ? R.drawable.camera_front : R.drawable.camera_back; mSwitchCameraBtn.setImageResource(res); } } diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt index 6c07f462..e0a92206 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt @@ -229,7 +229,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session videoCallPresenter = VideoCallPresenterImpl(this, baseUrl) mCallBtn = view.findViewById(R.id.btn_call) mCallBtn.setOnClickListener { - onCallClicked(it) + onCallClicked() } btnMinimize = view.findViewById(R.id.btn_minimize) btnMinimize.setOnClickListener { @@ -355,8 +355,8 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session override fun onError(session: Session, opentokError: OpentokError) { Log.d(TAG, "onError: Error (" + opentokError.message + ") in session " + session.sessionId) - videoCallResponseListener?.errorHandle("onError: Error (" + opentokError.message + ") in session ") - dialog?.dismiss() + videoCallResponseListener?.errorHandle("Error (" + opentokError.message + ") in session ") +// dialog?.dismiss() } override fun onStreamReceived(session: Session, stream: Stream) { @@ -393,8 +393,8 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session override fun onError(publisherKit: PublisherKit?, opentokError: OpentokError) { Log.d(VideoCallFragment.TAG, "onError: Error (" + opentokError.message + ") in publisher") - videoCallResponseListener?.errorHandle("onError: Error (" + opentokError.message + ") in publisher") - dialog?.dismiss() + videoCallResponseListener?.errorHandle("Error (" + opentokError.message + ") in publisher") +// dialog?.dismiss() } override fun onVideoDataReceived(subscriberKit: SubscriberKit?) { @@ -465,12 +465,12 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session if (mPublisher != null) { isSwitchCameraClicked = !isSwitchCameraClicked mPublisher!!.cycleCamera() - val res = if (isSwitchCameraClicked) R.drawable.flip_disapled else R.drawable.flip_enabled - mSwitchCameraBtn!!.setImageResource(res) + val res = if (isSwitchCameraClicked) R.drawable.camera_front else R.drawable.camera_back + mSwitchCameraBtn.setImageResource(res) } } - private fun onCallClicked(view: View?) { + fun onCallClicked() { disconnectSession() } @@ -487,6 +487,9 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session ) } isFullScreen = !isFullScreen + + val res = if (isFullScreen) R.drawable.reducing else R.drawable.expand + btnMinimize.setImageResource(res) setViewsVisibility() } @@ -518,7 +521,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session layoutName.visibility = View.VISIBLE mCameraBtn.visibility = View.VISIBLE mSwitchCameraBtn.visibility = View.VISIBLE - mspeckerBtn.visibility = View.VISIBLE +// mspeckerBtn.visibility = View.VISIBLE mMicBtn.visibility = View.VISIBLE mPublisherViewContainer.visibility = View.VISIBLE @@ -539,7 +542,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session layoutName.visibility = View.GONE mCameraBtn.visibility = View.GONE mSwitchCameraBtn.visibility = View.GONE - mspeckerBtn.visibility = View.GONE +// mspeckerBtn.visibility = View.GONE mMicBtn.visibility = View.GONE mPublisherViewContainer.visibility = View.GONE diff --git a/android/app/src/main/res/drawable/call.png b/android/app/src/main/res/drawable/call.png index fc00f4f942d7ef38336bf493a5e9ba2980692c55..52f8e267234d686e31be8a6e1b44287ab98364db 100644 GIT binary patch literal 21324 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 F@_+AV*hT;V literal 13229 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

*Md~6?Fz~6 zzsQ)=AsN)tmj4qmzOK4w*%;#<-M~yk%&+g{f$dc_+OBy)0BpCtlr&Bu-!3Bs<}4*f zZfoVhWSn-iuJyX^*p?C|nfOHjOt+U6dPAM~qLEnd#eDg>SW!QtLVVJ;4+( z#iu`WLE+kwkutepQjY+H*p8`kmPsRL7df6& z0S_-LB+~20W%oq7nX3*aIRZ>!qr1{QX-aZUq_|Mmpr*t)tb(4eE6majfd~*dFJjb& z)Xp(R(MT1z*0F){Me8Z)j@?Uu<97!H9}zT1`tWi$lBK;UBTyN#`BMGQ(+1uXK!yoahWIF>O91ir|9wpC+V6C*GELmgxN zU3e>iEOXQd;G_6zYgvgHVj$bmSs67k1VTpOQtqp*ZCr^M)S?PM(ZL4dts%D?210oDp>jJ)s< zbmVs%CWb%=2;htOWGCrN3^9|D|9jGyf9cl=M7uc{1n|LkbD!*FRA*vPy9{CHt^KyT z)1$DPJs@zoYY0`?;Ds0zCW9b`ZA~wbwd2qTe3kp4mwLSsBe&*V9qs@4d3iKeNDBn; zIl_5w;*}U;>`htrzBlWvAVZ)T0`JTBynZ2jB?fJffxIc5*Y21Y0z)9cYc{>Vo!>h# zawOQFGX2-EL!e`ItR({Y*hhV|ycc4K!M9}WJ5sORTOR;enb9JEZ@nX5>t&gs7h(Xl z3}&I_f)tXN7y>>Kz^5+cKIXIBX<5AygBoNw)3;t{3#OLa$+{vy5Aj~^TfJe>D>1NI z4PanoThx1dglktYTgkl-w&zjoofuT8hOqeOe2MP7VfNV&s6c?tfge{~_kL**V&qm| z)N$eeDSK~`6__dl_|9N!WP=eyOeZ#ErmE9+;8dTwwabYB)3UwIKvmT|7%`|^*T_ian8H@b$1! zh(YT`j(c_B4eAf_*~Ac-Lx8D%dJ2B;Gq{XK3|cC35XwC#-Qcr{A&?`$bS!HXqb&}P zN(?{{Ik4fV^f9T4A;1gLMqd;jofw55h#*hv{3pd{JFX=HpNbSr>yIwM5CbGcj;D0^ z>FB7K0b?NWkw`Jf;$4%*1w{-D7CC;b!_TD*&P~!sfWb8{T@LDf$iWc79M1^4=;Ac=t=h#c6)xZ`m?n!Fc0{^hs%yZIIom3xnz_QeK;RE4Ic) z?NlPj{Q~@7qz}ageB$=F4`7>5H|7lNw#%F7dBPlo16oQAgr1*{$RIma_y^nh$CKu z5X?4xEJ6ggdff$=WF1EYVb+SlIV zD4wJULRdw3MVMuagat6wM;7NG1V12RJh3*icEl(@S&@Y4U4H+;K;8CX-2sQS2L{x< z^)Jcs!<}Zj|E@JL%Fu`;59l)lLDy|P1Oi-@cK-j^DkBLoVpKdvk>s#0(~~?RO?5_# znI+!F!OI7t+Q%fDdlw^y1LAxxE#Ae5(E?Ht z1-uBkHlzhvLY)F;_6P!?kl~-}Hh@-}R~942%Fx}pQDk9ei_MJlI@CCY@c{$o85Ifa z{lEfcRWV|$id@ZoA`CrAK^Dlv=Ryg`=Ff|@6&B9;wkOQtIDgJ(oE{>4W8sic=t2G; X_h{;p&xF^300000NkvXXu0mjfo)PYq literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable/camera_front.png b/android/app/src/main/res/drawable/camera_front.png new file mode 100644 index 0000000000000000000000000000000000000000..332ab0f54dc44ca81ed878c6788b6accf5b35039 GIT binary patch literal 7100 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 M07*qoM6N<$f_}5^H~;_u diff --git a/android/app/src/main/res/drawable/flip_enabled.png b/android/app/src/main/res/drawable/flip_enabled.png deleted file mode 100644 index 152dc10e559c1d207f693225258f9a4ca77723f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6753 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 diff --git a/android/app/src/main/res/drawable/ic_mini.xml b/android/app/src/main/res/drawable/ic_mini.xml deleted file mode 100644 index 29df8da2..00000000 --- a/android/app/src/main/res/drawable/ic_mini.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/mic_disabled.png b/android/app/src/main/res/drawable/mic_disabled.png index 3603df754eb3df74c1cf7730a90df21bfa5f7337..266cb6c72418b5aba18b32c421c2b4e75f05e47e 100644 GIT binary patch literal 6537 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 VdluGjNo@cC002ovPDHLkV1l9-OF94m diff --git a/android/app/src/main/res/drawable/mic_enabled.png b/android/app/src/main/res/drawable/mic_enabled.png index 5d9aa6770cc43554a3767cf6b1c45fd3a8851410..ef7617f7ac43e7d7f55f48bcc1df718661b48f00 100644 GIT binary patch literal 5904 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 Bb}|3} diff --git a/android/app/src/main/res/drawable/reducing.png b/android/app/src/main/res/drawable/reducing.png new file mode 100644 index 0000000000000000000000000000000000000000..59f2c9e6faa03d35596a52689b19f99db9e09036 GIT binary patch literal 4953 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 PzPNO?j5Ob>+lTxQOg$37 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable/video_disanabled.png b/android/app/src/main/res/drawable/video_disanabled.png index 5c20c7bdecf06cb8401b40b1a27bce46cf35c4a9..d6ccbe7ad12d75da1c17e054c8af03e00273c8ef 100644 GIT binary patch literal 6438 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 diff --git a/android/app/src/main/res/drawable/video_enabled.png b/android/app/src/main/res/drawable/video_enabled.png index 23331e3083cbd75a5c17e37bbdd3e89a291141f7..6fcbe75041fb80538006600a8620f7081c215d2d 100644 GIT binary patch literal 5715 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 Becu28 literal 5338 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 he`HdmC}gyD$L5b3dLYnWBmD1P5u&cAR- @@ -89,18 +90,6 @@ android:src="@drawable/video_off_fill" /> - - @@ -152,6 +141,17 @@ app:layout_constraintStart_toEndOf="@id/btn_camera" app:layout_constraintTop_toTopOf="parent" /> + + diff --git a/android/app/src/main/res/values/dimens.xml b/android/app/src/main/res/values/dimens.xml index 2a4d695f..0694f5f4 100644 --- a/android/app/src/main/res/values/dimens.xml +++ b/android/app/src/main/res/values/dimens.xml @@ -29,7 +29,7 @@ 14sp 16sp - 24sp + 22sp 4dp From 259a63384e658cbaf18a722c57f9d234e2b363b9 Mon Sep 17 00:00:00 2001 From: mosazaid Date: Wed, 16 Jun 2021 09:53:28 +0300 Subject: [PATCH 13/30] fix draggable bugs --- .../com/hmg/hmgDr/ui/VideoCallActivity.java | 5 +- .../hmgDr/ui/fragment/VideoCallFragment.kt | 68 ++++++++++-------- ...ideo_disanabled.png => video_disabled.png} | Bin lib/config/config.dart | 4 +- 4 files changed, 42 insertions(+), 35 deletions(-) rename android/app/src/main/res/drawable/{video_disanabled.png => video_disabled.png} (100%) diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallActivity.java b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallActivity.java index daf372ed..75590b76 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallActivity.java +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallActivity.java @@ -20,7 +20,6 @@ import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; -import android.widget.Toast; import com.hmg.hmgDr.Model.ChangeCallStatusRequestModel; import com.hmg.hmgDr.Model.GetSessionStatusModel; @@ -63,7 +62,7 @@ public class VideoCallActivity extends AppCompatActivity implements EasyPermissi private Runnable mVolRunnable, mConnectedRunnable; private FrameLayout mPublisherViewContainer; - private RelativeLayout mSubscriberViewContainer; + private FrameLayout mSubscriberViewContainer; private RelativeLayout controlPanel; private String apiKey; @@ -432,7 +431,7 @@ public class VideoCallActivity extends AppCompatActivity implements EasyPermissi if (mPublisher != null) { isCameraClicked = !isCameraClicked; mPublisher.setPublishVideo(!isCameraClicked); - int res = isCameraClicked ? R.drawable.video_disanabled : R.drawable.video_enabled; + int res = isCameraClicked ? R.drawable.video_disabled : R.drawable.video_enabled; mCameraBtn.setImageResource(res); } } diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt index e0a92206..95f54ca8 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt @@ -17,6 +17,7 @@ import android.widget.* import androidx.annotation.Nullable import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet +import androidx.core.view.GestureDetectorCompat import androidx.fragment.app.DialogFragment import com.hmg.hmgDr.Model.GetSessionStatusModel import com.hmg.hmgDr.Model.SessionStatusModel @@ -97,8 +98,8 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session private var isConnected = false private var sessionStatusModel: GetSessionStatusModel? = null - private var videoCallResponseListener: VideoCallResponseListener? =null - + private var videoCallResponseListener: VideoCallResponseListener? = null + private lateinit var mDetector: GestureDetectorCompat override fun onCreate(savedInstanceState: Bundle?) { requireActivity().setTheme(R.style.AppTheme) @@ -144,12 +145,12 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session } } - fun setCallListener(videoCallResponseListener: VideoCallResponseListener){ + fun setCallListener(videoCallResponseListener: VideoCallResponseListener) { this.videoCallResponseListener = videoCallResponseListener } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View? { + savedInstanceState: Bundle?): View { parentView = inflater.inflate(R.layout.activity_video_call, container, false) @@ -166,10 +167,12 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session requestPermissions() handleDragDialog() + mDetector = GestureDetectorCompat(context, MyGestureListener { showControlPanelTemporarily() }) return parentView } + override fun onPause() { super.onPause() if (mSession == null) { @@ -258,18 +261,6 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session hiddenButtons() checkClientConnected() - mSubscriberViewContainer.setOnClickListener { - controlPanel!!.visibility = View.VISIBLE - mVolHandler!!.removeCallbacks(mVolRunnable!!) - mVolHandler!!.postDelayed(mVolRunnable!!, (5 * 1000).toLong()) - } - -// mSubscriberViewContainer!!.setOnTouchListener { v: View?, event: MotionEvent? -> -// controlPanel!!.visibility = View.VISIBLE -// mVolHandler!!.removeCallbacks(mVolRunnable!!) -// mVolHandler!!.postDelayed(mVolRunnable!!, (5 * 1000).toLong()) -// true -// } if (appLang == "ar") { progressBarLayout!!.layoutDirection = View.LAYOUT_DIRECTION_RTL } @@ -474,7 +465,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session disconnectSession() } - private fun onMinimizedClicked(view: View?) { + private fun onMinimizedClicked(view: View?) { if (isFullScreen) { dialog?.window?.setLayout( 400, @@ -494,10 +485,10 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session } private fun setViewsVisibility() { - val iconSize : Int = context!!.resources.getDimension(R.dimen.video_icon_size).toInt() - val iconSizeSmall : Int = context!!.resources.getDimension(R.dimen.video_icon_size_small).toInt() - val btnMinimizeLayoutParam : ConstraintLayout.LayoutParams = btnMinimize.layoutParams as ConstraintLayout.LayoutParams - val mCallBtnLayoutParam : ConstraintLayout.LayoutParams = mCallBtn.layoutParams as ConstraintLayout.LayoutParams + val iconSize: Int = context!!.resources.getDimension(R.dimen.video_icon_size).toInt() + val iconSizeSmall: Int = context!!.resources.getDimension(R.dimen.video_icon_size_small).toInt() + val btnMinimizeLayoutParam: ConstraintLayout.LayoutParams = btnMinimize.layoutParams as ConstraintLayout.LayoutParams + val mCallBtnLayoutParam: ConstraintLayout.LayoutParams = mCallBtn.layoutParams as ConstraintLayout.LayoutParams // val localPreviewMargin : Int = context!!.resources.getDimension(R.dimen.local_preview_margin_top).toInt() // val localPreviewWidth : Int = context!!.resources.getDimension(R.dimen.local_preview_width).toInt() @@ -512,7 +503,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session val remotePreviewIconSize: Int = context!!.resources.getDimension(R.dimen.remote_back_icon_size).toInt() val remotePreviewIconSizeSmall: Int = context!!.resources.getDimension(R.dimen.remote_back_icon_size_small).toInt() - val remotePreviewLayoutParam : FrameLayout.LayoutParams = mSubscriberViewIcon.layoutParams as FrameLayout.LayoutParams + val remotePreviewLayoutParam: FrameLayout.LayoutParams = mSubscriberViewIcon.layoutParams as FrameLayout.LayoutParams val constraintSet = ConstraintSet() //layoutParam.constrain @@ -574,7 +565,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session if (mPublisher != null) { isCameraClicked = !isCameraClicked mPublisher!!.publishVideo = !isCameraClicked - val res = if (isCameraClicked) R.drawable.video_disanabled else R.drawable.video_enabled + val res = if (isCameraClicked) R.drawable.video_disabled else R.drawable.video_enabled mCameraBtn!!.setImageResource(res) } } @@ -604,12 +595,13 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session getWindowManagerDefaultDisplay() videoCallContainer.setOnTouchListener(dragListener) -// mSubscriberViewContainer.setOnTouchListener(dragListener) + mSubscriberViewContainer.setOnTouchListener(dragListener) } @SuppressLint("ClickableViewAccessibility") - val dragListener : View.OnTouchListener = View.OnTouchListener{ _, event -> - //Get Floating widget view params + val dragListener: View.OnTouchListener = View.OnTouchListener { _, event -> + mDetector.onTouchEvent(event) + //Get Floating widget view params val layoutParams: WindowManager.LayoutParams = dialog!!.window!!.attributes //get the touch location coordinates @@ -636,11 +628,12 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session val barHeight: Int = getStatusBarHeight() if (y_cord_Destination < 0) { // y_cord_Destination = 0 - y_cord_Destination = - -(szWindow.y - (videoCallContainer.height /*+ barHeight*/)) +// y_cord_Destination = +// -(szWindow.y - (videoCallContainer.height /*+ barHeight*/)) + y_cord_Destination = - (szWindow.y/2) } else if (y_cord_Destination + (videoCallContainer.height + barHeight) > szWindow.y) { - y_cord_Destination = - szWindow.y - (videoCallContainer.height + barHeight) +// y_cord_Destination = szWindow.y - (videoCallContainer.height + barHeight) + y_cord_Destination = (szWindow.y/2) } layoutParams.y = y_cord_Destination @@ -662,6 +655,12 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session true } + private fun showControlPanelTemporarily() { + controlPanel!!.visibility = View.VISIBLE + mVolHandler!!.removeCallbacks(mVolRunnable!!) + mVolHandler!!.postDelayed(mVolRunnable!!, (5 * 1000).toLong()) + } + /* Reset position of Floating Widget view on dragging */ private fun resetPosition(x_cord_now: Int) { if (x_cord_now <= szWindow.x / 2) { @@ -741,6 +740,15 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session ).toInt() } + private class MyGestureListener(val onTabCall: () -> Unit) : GestureDetector.SimpleOnGestureListener() { + + override fun onSingleTapConfirmed(event: MotionEvent): Boolean { + onTabCall() + return true + } + + } + companion object { @JvmStatic fun newInstance(args: Bundle) = diff --git a/android/app/src/main/res/drawable/video_disanabled.png b/android/app/src/main/res/drawable/video_disabled.png similarity index 100% rename from android/app/src/main/res/drawable/video_disanabled.png rename to android/app/src/main/res/drawable/video_disabled.png diff --git a/lib/config/config.dart b/lib/config/config.dart index 8241b36c..eb361798 100644 --- a/lib/config/config.dart +++ b/lib/config/config.dart @@ -5,8 +5,8 @@ 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 = 'https://hmgwebservices.com/'; -// const BASE_URL = 'https://uat.hmgwebservices.com/'; +// const BASE_URL = 'https://hmgwebservices.com/'; +const BASE_URL = 'https://uat.hmgwebservices.com/'; const PHARMACY_ITEMS_URL = "Services/Lists.svc/REST/GetPharmcyItems_Region_enh"; const PHARMACY_LIST_URL = "Services/Patients.svc/REST/GetPharmcyList"; const PATIENT_PROGRESS_NOTE_URL = "Services/DoctorApplication.svc/REST/GetProgressNoteForInPatient"; From 1ed22e68e3f96b113d569abeeef5f441e5652ae1 Mon Sep 17 00:00:00 2001 From: mosazaid Date: Wed, 16 Jun 2021 10:13:58 +0300 Subject: [PATCH 14/30] video fix bugs --- .../hmgDr/ui/fragment/VideoCallFragment.kt | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt index 95f54ca8..f7f70e40 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt @@ -325,7 +325,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session Log.i(TAG, "Session Connected") mPublisher = Publisher.Builder(requireContext()).build() mPublisher!!.setPublisherListener(this) - mPublisherViewContainer!!.addView(mPublisher!!.view) + mPublisherViewContainer.addView(mPublisher!!.view) if (mPublisher!!.getView() is GLSurfaceView) { (mPublisher!!.getView() as GLSurfaceView).setZOrderOnTop(true) } @@ -490,42 +490,37 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session val btnMinimizeLayoutParam: ConstraintLayout.LayoutParams = btnMinimize.layoutParams as ConstraintLayout.LayoutParams val mCallBtnLayoutParam: ConstraintLayout.LayoutParams = mCallBtn.layoutParams as ConstraintLayout.LayoutParams -// val localPreviewMargin : Int = context!!.resources.getDimension(R.dimen.local_preview_margin_top).toInt() -// val localPreviewWidth : Int = context!!.resources.getDimension(R.dimen.local_preview_width).toInt() -// val localPreviewHeight : Int = context!!.resources.getDimension(R.dimen.local_preview_height).toInt() + val localPreviewMargin : Int = context!!.resources.getDimension(R.dimen.local_preview_margin_top).toInt() + val localPreviewWidth : Int = context!!.resources.getDimension(R.dimen.local_preview_width).toInt() + val localPreviewHeight : Int = context!!.resources.getDimension(R.dimen.local_preview_height).toInt() // val localPreviewIconSize: Int = context!!.resources.getDimension(R.dimen.local_back_icon_size).toInt() // val localPreviewMarginSmall : Int = context!!.resources.getDimension(R.dimen.local_preview_margin_small).toInt() // val localPreviewWidthSmall : Int = context!!.resources.getDimension(R.dimen.local_preview_width_small).toInt() // val localPreviewHeightSmall : Int = context!!.resources.getDimension(R.dimen.local_preview_height_small).toInt() // val localPreviewIconSmall: Int = context!!.resources.getDimension(R.dimen.local_back_icon_size_small).toInt() // val localPreviewLayoutIconParam : FrameLayout.LayoutParams -// val localPreviewLayoutParam : RelativeLayout.LayoutParams = mPublisherViewContainer.layoutParams as RelativeLayout.LayoutParams + val localPreviewLayoutParam : RelativeLayout.LayoutParams = mPublisherViewContainer.layoutParams as RelativeLayout.LayoutParams val remotePreviewIconSize: Int = context!!.resources.getDimension(R.dimen.remote_back_icon_size).toInt() val remotePreviewIconSizeSmall: Int = context!!.resources.getDimension(R.dimen.remote_back_icon_size_small).toInt() val remotePreviewLayoutParam: FrameLayout.LayoutParams = mSubscriberViewIcon.layoutParams as FrameLayout.LayoutParams - val constraintSet = ConstraintSet() - //layoutParam.constrain -// constraintSet. if (isFullScreen) { layoutName.visibility = View.VISIBLE mCameraBtn.visibility = View.VISIBLE mSwitchCameraBtn.visibility = View.VISIBLE // mspeckerBtn.visibility = View.VISIBLE mMicBtn.visibility = View.VISIBLE - mPublisherViewContainer.visibility = View.VISIBLE -// layoutParam = ConstraintLayout.LayoutParams(iconSize, iconSize) btnMinimizeLayoutParam.width = iconSize btnMinimizeLayoutParam.height = iconSize mCallBtnLayoutParam.width = iconSize mCallBtnLayoutParam.height = iconSize // localPreviewLayoutIconParam = FrameLayout.LayoutParams(localPreviewIconSize, localPreviewIconSize) //// localPreviewLayoutParam = RelativeLayout.LayoutParams(localPreviewWidth, localPreviewHeight) -// localPreviewLayoutParam.width = localPreviewIconSize -// localPreviewLayoutParam.height = localPreviewIconSize -// localPreviewLayoutParam.setMargins(0,localPreviewMargin, localPreviewMargin, 0) + localPreviewLayoutParam.width = localPreviewWidth + localPreviewLayoutParam.height = localPreviewHeight + localPreviewLayoutParam.setMargins(0,localPreviewMargin, localPreviewMargin, 0) // remotePreviewLayoutParam = FrameLayout.LayoutParams(remotePreviewIconSize, remotePreviewIconSize) remotePreviewLayoutParam.width = remotePreviewIconSize remotePreviewLayoutParam.height = remotePreviewIconSize @@ -535,7 +530,8 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session mSwitchCameraBtn.visibility = View.GONE // mspeckerBtn.visibility = View.GONE mMicBtn.visibility = View.GONE - mPublisherViewContainer.visibility = View.GONE +// mPublisherViewContainer.visibility = View.GONE +// mPublisherViewIcon.visibility = View.GONE // layoutParam = ConstraintLayout.LayoutParams(iconSizeSmall, iconSizeSmall) btnMinimizeLayoutParam.width = iconSizeSmall @@ -543,6 +539,9 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session mCallBtnLayoutParam.width = iconSizeSmall mCallBtnLayoutParam.height = iconSizeSmall + localPreviewLayoutParam.width = 0 + localPreviewLayoutParam.height = 0 + localPreviewLayoutParam.setMargins(0,localPreviewMargin / 2, localPreviewMargin/ 2, 0) // localPreviewLayoutIconParam = FrameLayout.LayoutParams(localPreviewIconSmall, localPreviewIconSmall) //// localPreviewLayoutParam = RelativeLayout.LayoutParams(localPreviewWidthSmall, localPreviewHeightSmall) // localPreviewLayoutParam.width = localPreviewWidthSmall @@ -553,7 +552,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session remotePreviewLayoutParam.height = remotePreviewIconSizeSmall } -// mPublisherViewContainer.layoutParams = localPreviewLayoutParam + mPublisherViewContainer.layoutParams = localPreviewLayoutParam // mPublisherViewIcon.layoutParams = localPreviewLayoutIconParam mSubscriberViewIcon.layoutParams = remotePreviewLayoutParam From 7c7aa781aa67e9d68f0e989f9d70e4f7f6b0763b Mon Sep 17 00:00:00 2001 From: mosazaid Date: Wed, 16 Jun 2021 11:12:48 +0300 Subject: [PATCH 15/30] video fix bugs --- .../main/kotlin/com/hmg/hmgDr/MainActivity.kt | 74 ++++++++++--------- .../hmg/hmgDr/ui/VideoCallResponseListener.kt | 2 + .../hmgDr/ui/fragment/VideoCallFragment.kt | 6 +- 3 files changed, 44 insertions(+), 38 deletions(-) diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt index 9d9581fe..7ddc03e4 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt @@ -20,6 +20,7 @@ import io.flutter.plugins.GeneratedPluginRegistrant class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, VideoCallResponseListener { private val CHANNEL = "Dr.cloudSolution/videoCall" + private lateinit var methodChannel: MethodChannel private var result: MethodChannel.Result? = null private var call: MethodCall? = null private val LAUNCH_VIDEO: Int = 1 @@ -29,15 +30,10 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine) - MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler(this) + methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL) + methodChannel.setMethodCallHandler(this) } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - } - - override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { this.result = result @@ -67,7 +63,7 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, "closeVideoCall" -> { dialogFragment?.onCallClicked() } - "onCallConnected"->{ + "onCallConnected" -> { } else -> { @@ -87,7 +83,7 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, // intent.putExtra("sessionStatusModel", sessionStatusModel) // startActivityForResult(intent, LAUNCH_VIDEO) - if (dialogFragment == null){ + if (dialogFragment == null) { val arguments = Bundle() arguments.putString("apiKey", apiKey) arguments.putString("sessionId", sessionId) @@ -103,47 +99,47 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, it.isCancelable = true it.show(transaction, "dialog") } - } else if (!dialogFragment!!.isVisible){ + } else if (!dialogFragment!!.isVisible) { val transaction = supportFragmentManager.beginTransaction() dialogFragment!!.show(transaction, "dialog") } } - /* override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - var asd = ""; - if (requestCode == LAUNCH_VIDEO) { - if (resultCode == Activity.RESULT_OK) { - val result : SessionStatusModel? = data?.getParcelableExtra("sessionStatusNotRespond") - val callResponse : HashMap = HashMap() + /* override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + var asd = ""; + if (requestCode == LAUNCH_VIDEO) { + if (resultCode == Activity.RESULT_OK) { + val result : SessionStatusModel? = data?.getParcelableExtra("sessionStatusNotRespond") + val callResponse : HashMap = HashMap() - val sessionStatus : HashMap = HashMap() - val gson = GsonBuilder().serializeNulls().create() + val sessionStatus : HashMap = HashMap() + val gson = GsonBuilder().serializeNulls().create() - callResponse["callResponse"] = "CallNotRespond" - val jsonRes = gson.toJson(result) - callResponse["sessionStatus"] = jsonRes + callResponse["callResponse"] = "CallNotRespond" + val jsonRes = gson.toJson(result) + callResponse["sessionStatus"] = jsonRes - this.result?.success(callResponse) - } - if (resultCode == Activity.RESULT_CANCELED) { - val callResponse : HashMap = HashMap() - callResponse["callResponse"] = "CallEnd" + this.result?.success(callResponse) + } + if (resultCode == Activity.RESULT_CANCELED) { + val callResponse : HashMap = HashMap() + callResponse["callResponse"] = "CallEnd" - result?.success(callResponse) - } - } - }*/ + result?.success(callResponse) + } + } + }*/ override fun onCallFinished(resultCode: Int, intent: Intent?) { dialogFragment = null if (resultCode == Activity.RESULT_OK) { - val result : SessionStatusModel? = intent?.getParcelableExtra("sessionStatusNotRespond") - val callResponse : HashMap = HashMap() + val result: SessionStatusModel? = intent?.getParcelableExtra("sessionStatusNotRespond") + val callResponse: HashMap = HashMap() - val sessionStatus : HashMap = HashMap() + val sessionStatus: HashMap = HashMap() val gson = GsonBuilder().serializeNulls().create() callResponse["callResponse"] = "CallNotRespond" @@ -152,9 +148,8 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, this.result?.success(callResponse) } else if (resultCode == Activity.RESULT_CANCELED) { - val callResponse : HashMap = HashMap() + val callResponse: HashMap = HashMap() callResponse["callResponse"] = "CallEnd" - result?.success(callResponse) } } @@ -164,5 +159,12 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, // Toast.makeText(this, message, Toast.LENGTH_LONG).show() } + override fun minimizeVideoEvent(isMinimize: Boolean) { + if (isMinimize) + methodChannel.invokeMethod("onCallConnected", null) + else + methodChannel.invokeMethod("onCallDisconnected", null) + } + } diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallResponseListener.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallResponseListener.kt index d2ed15e0..eceed01e 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallResponseListener.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallResponseListener.kt @@ -7,4 +7,6 @@ interface VideoCallResponseListener { fun onCallFinished(resultCode : Int, intent: Intent? = null) fun errorHandle(message: String) + + fun minimizeVideoEvent(isMinimize : Boolean) } \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt index f7f70e40..03883361 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt @@ -420,13 +420,13 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session return } if (mSubscriber != null) { - mSubscriberViewContainer!!.removeView(mSubscriber!!.view) + mSubscriberViewContainer.removeView(mSubscriber!!.view) mSession!!.unsubscribe(mSubscriber) mSubscriber!!.destroy() mSubscriber = null } if (mPublisher != null) { - mPublisherViewContainer!!.removeView(mPublisher!!.view) + mPublisherViewContainer.removeView(mPublisher!!.view) mSession!!.unpublish(mPublisher) mPublisher!!.destroy() mPublisher = null @@ -482,6 +482,8 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session val res = if (isFullScreen) R.drawable.reducing else R.drawable.expand btnMinimize.setImageResource(res) setViewsVisibility() + + videoCallResponseListener?.minimizeVideoEvent(!isFullScreen) } private fun setViewsVisibility() { From 5312c221d129d5a0200e7e8cb7b35800fa4e6014 Mon Sep 17 00:00:00 2001 From: mosazaid Date: Wed, 16 Jun 2021 11:14:32 +0300 Subject: [PATCH 16/30] video fix bugs --- .../kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt index 03883361..1dc3d267 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt @@ -19,6 +19,7 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.core.view.GestureDetectorCompat import androidx.fragment.app.DialogFragment +import com.hmg.hmgDr.Model.ChangeCallStatusRequestModel import com.hmg.hmgDr.Model.GetSessionStatusModel import com.hmg.hmgDr.Model.SessionStatusModel import com.hmg.hmgDr.R @@ -358,7 +359,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session } isConnected = true subscribeToStream(stream) -// videoCallPresenter.callChangeCallStatus(ChangeCallStatusRequestModel(3, sessionStatusModel!!.doctorId, sessionStatusModel!!.generalid, token, sessionStatusModel!!.vcid)) + videoCallPresenter.callChangeCallStatus(ChangeCallStatusRequestModel(3, sessionStatusModel!!.doctorId, sessionStatusModel!!.generalid, token, sessionStatusModel!!.vcid)) } override fun onStreamDropped(session: Session, stream: Stream) { @@ -433,7 +434,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session } mSession!!.disconnect() countDownTimer?.cancel() -// videoCallPresenter.callChangeCallStatus(ChangeCallStatusRequestModel(16, sessionStatusModel!!.doctorId, sessionStatusModel!!.generalid, token, sessionStatusModel!!.vcid)) + videoCallPresenter.callChangeCallStatus(ChangeCallStatusRequestModel(16, sessionStatusModel!!.doctorId, sessionStatusModel!!.generalid, token, sessionStatusModel!!.vcid)) dialog?.dismiss() } @@ -589,7 +590,6 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session } } - @SuppressLint("ClickableViewAccessibility") fun handleDragDialog() { mWindowManager = requireActivity().getSystemService(Context.WINDOW_SERVICE) as WindowManager From c1de90877749e897f4d456cf2825a96bd02452cf Mon Sep 17 00:00:00 2001 From: Zohaib Iqbal Kambrani <> Date: Wed, 16 Jun 2021 11:16:27 +0300 Subject: [PATCH 17/30] no message --- ios/Runner/Base.lproj/Main.storyboard | 161 +++++++++++--------- ios/Runner/Extensions.swift | 13 +- ios/Runner/MainAppViewController.swift | 30 ++-- ios/Runner/VideoCallRequestParameters.swift | 4 +- ios/Runner/VideoCallViewController.swift | 91 ++++++----- 5 files changed, 167 insertions(+), 132 deletions(-) diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard index c1c16274..2bdee5a2 100755 --- a/ios/Runner/Base.lproj/Main.storyboard +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -41,18 +41,94 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -210,76 +286,9 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -300,23 +309,25 @@ + + + - - + diff --git a/ios/Runner/Extensions.swift b/ios/Runner/Extensions.swift index c73f49e2..b79c8deb 100644 --- a/ios/Runner/Extensions.swift +++ b/ios/Runner/Extensions.swift @@ -9,12 +9,19 @@ import AADraggableView extension AADraggableView{ func enable(_ enable:Bool){ - if enable == false{ + isEnabled = enable + if enable == false{ gestureRecognizers?.forEach({ gest in removeGestureRecognizer(gest) }) - }else{ - isEnabled = true } } } + +extension UIView{ + func hidden(_ hidden:Bool, rootView:UIView){ + UIView.transition(with: rootView, duration: 0.5, options: .transitionCrossDissolve, animations: { + self.isHidden = hidden + }) + } +} diff --git a/ios/Runner/MainAppViewController.swift b/ios/Runner/MainAppViewController.swift index ffca444c..c7b7a18d 100644 --- a/ios/Runner/MainAppViewController.swift +++ b/ios/Runner/MainAppViewController.swift @@ -72,9 +72,7 @@ extension MainAppViewController : ICallProtocol{ } private func showVideo(show:Bool){ - UIView.transition(with: view, duration: 0.5, options: .transitionCrossDissolve, animations: { - self.videoCallContainer.isHidden = !show - }) + self.videoCallContainer.hidden(!show, rootView: view) } private func startVideoCall(result: @escaping FlutterResult, call:FlutterMethodCall) { @@ -83,23 +81,19 @@ extension MainAppViewController : ICallProtocol{ if let arguments = call.arguments as? NSDictionary{ showVideoCallView(true) - videoCallViewController.onRectFloat = { min in - self.rectFloatVideoCallView(min) - if(min){ - self.videoCallContainer.repositionIfNeeded() - } + videoCallViewController.onRectFloat = { isRectFloat in + self.rectFloatVideoCallView(isRectFloat) } - videoCallViewController.onCircleFloat = { min in - self.circleFloatVideoCallView(min) - self.videoCallContainer.reposition = min ? .free : .edgesOnly - self.videoCallContainer.repositionIfNeeded() + videoCallViewController.onCircleFloat = { isCircleFloat in + self.circleFloatVideoCallView(isCircleFloat) } videoCallViewController.onCallConnect = { self.videoCallChannel?.invokeMethod("onCallConnected",arguments: nil) } videoCallViewController.onCallDisconnect = { self.showVideoCallView(false) + self.videoCallViewController.minimizeVideoState(state: false) self.videoCallChannel?.invokeMethod("onCallDisconnected",arguments: nil) } videoCallViewController.callBack = self @@ -130,7 +124,7 @@ extension MainAppViewController : ICallProtocol{ } private func circleFloatVideoCallView(_ value:Bool){ - videoCallContainer.enable(value) + videoCallContainer.reposition = value ? .sticky : .edgesOnly UIView.animate(withDuration: 0.5) { if(value){ @@ -152,9 +146,7 @@ extension MainAppViewController : ICallProtocol{ } private func showVideoCallView(_ value:Bool){ - UIView.animate(withDuration: 0.5) { - self.videoCallContainer.isHidden = !value - } + self.videoCallContainer.hidden(!value, rootView: view) } func sessionDone(res: Any) { @@ -182,14 +174,14 @@ extension MainAppViewController : ICallProtocol{ videoCallContainer.heightAnchor.constraint(equalToConstant: screen.height) ] vdoCallViewFloatRectConstraint = [ - videoCallContainer.topAnchor.constraint(equalTo: view.topAnchor, constant: 20), + videoCallContainer.topAnchor.constraint(equalTo: view.topAnchor, constant: 40), videoCallContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), videoCallContainer.widthAnchor.constraint(equalToConstant: screen.width/3), videoCallContainer.heightAnchor.constraint(equalToConstant: screen.height/3.5) ] vdoCallViewFloatCircleConstraint = [ - videoCallContainer.topAnchor.constraint(equalTo: view.topAnchor), - videoCallContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor), + videoCallContainer.topAnchor.constraint(equalTo: view.topAnchor, constant: 40), + videoCallContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), videoCallContainer.widthAnchor.constraint(equalToConstant: 70), videoCallContainer.heightAnchor.constraint(equalToConstant: 70) ] diff --git a/ios/Runner/VideoCallRequestParameters.swift b/ios/Runner/VideoCallRequestParameters.swift index 2366c562..543d7e8b 100644 --- a/ios/Runner/VideoCallRequestParameters.swift +++ b/ios/Runner/VideoCallRequestParameters.swift @@ -12,7 +12,8 @@ class VideoCallRequestParameters{ var generalId:String? var doctorId:Int? var baseUrl:String? - + var patientName:String? + init(dictionary:NSDictionary){ self.apiKey = dictionary["kApiKey"] as? String self.sessionId = dictionary["kSessionId"] as? String @@ -23,5 +24,6 @@ class VideoCallRequestParameters{ self.generalId = dictionary["generalId"] as? String self.doctorId = dictionary["DoctorId"] as? Int self.baseUrl = dictionary["baseUrl"] as? String + self.patientName = dictionary["patientName"] as? String } } diff --git a/ios/Runner/VideoCallViewController.swift b/ios/Runner/VideoCallViewController.swift index 02a70a0c..83865c6e 100644 --- a/ios/Runner/VideoCallViewController.swift +++ b/ios/Runner/VideoCallViewController.swift @@ -57,14 +57,15 @@ class VideoCallViewController: UIViewController { @IBOutlet weak var remoteVideoMutedIndicator: UIImageView! @IBOutlet weak var localVideoMutedBg: UIView! + @IBOutlet weak var btnScreenTap: UIButton! @IBOutlet weak var localVideoContainer: UIView! @IBOutlet weak var topBar: UIView! @IBOutlet weak var lblCallDuration: UILabel! - @IBOutlet weak var remoteVideo: UIView! - @IBOutlet weak var localVideo: UIView!{ + @IBOutlet weak var fullVideoView: UIView! + @IBOutlet weak var smallVideoView: UIView!{ didSet{ - localVideo.layer.borderColor = UIColor.white.cgColor - localVideoDraggable = localVideo?.superview as? AADraggableView + smallVideoView.layer.borderColor = UIColor.white.cgColor + localVideoDraggable = smallVideoView?.superview as? AADraggableView localVideoDraggable?.reposition = .edgesOnly } } @@ -78,16 +79,28 @@ class VideoCallViewController: UIViewController { gesture.view?.removeFromSuperview() } - @IBAction func onVideoContainerTapped(_ sender: Any) { + @IBAction func btnOnScreenTapped(_ sender: Any) { if(hideVideoBtn.isSelected){ circleFloatBtnTapped(hideVideoBtn) }else if(btnMinimize.isSelected){ btnMinimizeTapped(btnMinimize) - }else if(!btnMinimize.isSelected){ - // Swipe video here } } + + @IBAction func btnSwipeVideoTapped(_ sender: Any) { +// let smallVdoRender = smallVideoView.subviews.first +// let fullVdoRender = fullVideoView.subviews.first +// if let vdo = smallVdoRender{ +// fullVideoView.addSubview(vdo) +// } +// if let vdo = fullVdoRender{ +// smallVideoView.addSubview(vdo) +// } +// +// layoutVideoRenderViews() + } + @IBAction func didClickMuteButton(_ sender: UIButton) { sender.isSelected = !sender.isSelected publisher!.publishAudio = !sender.isSelected @@ -105,7 +118,7 @@ class VideoCallViewController: UIViewController { } else { publisher!.publishVideo = true } - localVideo.isHidden = sender.isSelected + smallVideoView.isHidden = sender.isSelected localVideoMutedBg.isHidden = !sender.isSelected } @@ -130,36 +143,41 @@ class VideoCallViewController: UIViewController { onCircleFloat?(sender.isSelected) topBar.isHidden = sender.isSelected controlButtons.isHidden = sender.isSelected - localVideo.isHidden = sender.isSelected + smallVideoView.isHidden = sender.isSelected self.publisher?.view?.layoutIfNeeded() } - var floated = false @IBAction func btnMinimizeTapped(_ sender: UIButton) { - floated = !floated - onRectFloat?(floated) - sender.isSelected = floated + minimizeVideoState(state: !sender.isSelected) + btnScreenTap.isHidden = !sender.isSelected + } + + func minimizeVideoState(state:Bool){ + btnMinimize.isSelected = state + onRectFloat?(state) - NSLayoutConstraint.activate(floated ? minimizeConstraint : maximisedConstraint) - NSLayoutConstraint.deactivate(floated ? maximisedConstraint : minimizeConstraint) - localVideoDraggable?.enable(!floated) + NSLayoutConstraint.activate(state ? minimizeConstraint : maximisedConstraint) + NSLayoutConstraint.deactivate(state ? maximisedConstraint : minimizeConstraint) + localVideoDraggable?.enable(!state) - lblRemoteUsername.isHidden = floated - hideVideoBtn.isHidden = !floated + lblRemoteUsername.isHidden = state + hideVideoBtn.isHidden = !state lblCallDuration.superview?.isHidden = !hideVideoBtn.isHidden - let min_ = floated UIView.animate(withDuration: 0.5) { - self.videoMuteBtn.isHidden = min_ - self.micMuteBtn.isHidden = min_ - self.camSwitchBtn.isHidden = min_ - - let localVdoSize = self.localVideo.bounds.size - let remoteVdoSize = self.remoteVideo.bounds.size - self.publisher?.view?.frame = CGRect(x: 0, y: 0, width: localVdoSize.width, height: localVdoSize.height) - self.subscriber?.view?.frame = CGRect(x: 0, y: 0, width: remoteVdoSize.width, height: remoteVdoSize.height) - self.publisher?.view?.layoutIfNeeded() - self.subscriber?.view?.layoutIfNeeded() + self.videoMuteBtn.isHidden = state + self.micMuteBtn.isHidden = state + self.camSwitchBtn.isHidden = state + self.layoutVideoRenderViews() + } + } + + func layoutVideoRenderViews(){ + if let publisherVdoSize = publisher?.view?.superview?.bounds.size{ + publisher?.view?.frame = CGRect(x: 0, y: 0, width: publisherVdoSize.width, height: publisherVdoSize.height) + } + if let subscriberVdoSize = subscriber?.view?.superview?.bounds.size{ + subscriber?.view?.frame = CGRect(x: 0, y: 0, width: subscriberVdoSize.width, height: subscriberVdoSize.height) } } @@ -179,6 +197,9 @@ class VideoCallViewController: UIViewController { } func start(params:VideoCallRequestParameters){ + lblRemoteUsername.text = params.patientName ?? "- - -" + btnScreenTap.isHidden = true + hideVideoBtn.isHidden = true self.kApiKey = params.apiKey ?? "" self.kSessionId = params.sessionId ?? "" @@ -193,6 +214,7 @@ class VideoCallViewController: UIViewController { requestCameraPermissionsIfNeeded() hideVideoMuted() setupSession() + } private func changeCallStatus(callStatus:Int){ @@ -397,10 +419,11 @@ extension VideoCallViewController: OTSessionDelegate { showAlert(error?.localizedDescription) } - publisher?.view!.frame = CGRect(x: 0, y: 0, width: localVideo.bounds.size.width, height: localVideo.bounds.size.height) + publisher?.view?.tag = 11 publisher?.view?.layer.cornerRadius = 5 publisher?.view?.clipsToBounds = true - localVideo.addSubview((publisher?.view)!) + smallVideoView.addSubview((publisher?.view)!) + layoutVideoRenderViews() } func sessionDidDisconnect(_ session: OTSession) { @@ -440,9 +463,9 @@ extension VideoCallViewController: OTSessionDelegate { guard let subscriberView = subscriber.view else { return } - - subscriberView.frame = CGRect(x: 0, y: 0, width: remoteVideo.bounds.width, height: remoteVideo.bounds.height) - remoteVideo.addSubview(subscriberView) + subscriberView.tag = 22 + fullVideoView.addSubview(subscriberView) + layoutVideoRenderViews() startUpdateCallDuration() onCallConnect?() From 0868de7961ab6053cd50e61216fd7b471498ef09 Mon Sep 17 00:00:00 2001 From: mosazaid Date: Wed, 16 Jun 2021 14:05:04 +0300 Subject: [PATCH 18/30] video fix bugs --- android/app/src/main/AndroidManifest.xml | 1 - .../main/kotlin/com/hmg/hmgDr/MainActivity.kt | 28 +- .../com/hmg/hmgDr/ui/VideoCallActivity.java | 481 ------------------ .../hmg/hmgDr/ui/VideoCallResponseListener.kt | 2 + .../hmgDr/ui/fragment/VideoCallFragment.kt | 34 +- .../src/main/res/drawable/circle_shape.xml | 14 + .../main/res/drawable/layout_rounded_bg.xml | 14 +- 7 files changed, 64 insertions(+), 510 deletions(-) delete mode 100644 android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallActivity.java create mode 100644 android/app/src/main/res/drawable/circle_shape.xml diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 0e71249f..bf0d3766 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -27,7 +27,6 @@ android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:label="HMG Doctor"> - = HashMap() callResponse["callResponse"] = "CallEnd" @@ -166,5 +167,8 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, methodChannel.invokeMethod("onCallDisconnected", null) } + override fun onBackPressed() { + super.onBackPressed() + } } diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallActivity.java b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallActivity.java deleted file mode 100644 index 75590b76..00000000 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallActivity.java +++ /dev/null @@ -1,481 +0,0 @@ -package com.hmg.hmgDr.ui; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AppCompatActivity; - -import android.Manifest; -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.Intent; -import android.opengl.GLSurfaceView; -import android.os.Bundle; -import android.os.CountDownTimer; -import android.os.Handler; -import android.os.SystemClock; -import android.util.Log; -import android.view.View; -import android.widget.Chronometer; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.RelativeLayout; -import android.widget.TextView; - -import com.hmg.hmgDr.Model.ChangeCallStatusRequestModel; -import com.hmg.hmgDr.Model.GetSessionStatusModel; -import com.hmg.hmgDr.Model.SessionStatusModel; -import com.hmg.hmgDr.R; -import com.opentok.android.Session; -import com.opentok.android.Stream; -import com.opentok.android.Publisher; -import com.opentok.android.PublisherKit; -import com.opentok.android.Subscriber; -import com.opentok.android.BaseVideoRenderer; -import com.opentok.android.OpentokError; -import com.opentok.android.SubscriberKit; - -import java.util.List; -import java.util.Objects; - -import pub.devrel.easypermissions.AfterPermissionGranted; -import pub.devrel.easypermissions.AppSettingsDialog; -import pub.devrel.easypermissions.EasyPermissions; - -public class VideoCallActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks, - Session.SessionListener, - Publisher.PublisherListener, - Subscriber.VideoListener, VideoCallContract.VideoCallView { - - private static final String TAG = VideoCallActivity.class.getSimpleName(); - - VideoCallContract.VideoCallPresenter videoCallPresenter; - - private static final int RC_SETTINGS_SCREEN_PERM = 123; - private static final int RC_VIDEO_APP_PERM = 124; - - - private Session mSession; - private Publisher mPublisher; - private Subscriber mSubscriber; - - private Handler mVolHandler, mConnectedHandler; - private Runnable mVolRunnable, mConnectedRunnable; - - private FrameLayout mPublisherViewContainer; - private FrameLayout mSubscriberViewContainer; - private RelativeLayout controlPanel; - - private String apiKey; - private String sessionId; - private String token; - private String appLang; - private String baseUrl; - - private boolean isSwitchCameraClicked; - private boolean isCameraClicked; - private boolean isSpeckerClicked; - private boolean isMicClicked; - - private TextView patientName; - private Chronometer cmTimer; - long elapsedTime; - Boolean resume = false; - - private ImageView mCallBtn; - private ImageView btnMinimize; - private ImageView mCameraBtn; - private ImageView mSwitchCameraBtn; - private ImageView mspeckerBtn; - private ImageView mMicBtn; - - private ProgressBar progressBar; - private CountDownTimer countDownTimer; - private TextView progressBarTextView; - private RelativeLayout progressBarLayout; - - private boolean isConnected = false; - - private GetSessionStatusModel sessionStatusModel; - - - @Override - protected void onCreate(Bundle savedInstanceState) { - setTheme(R.style.AppTheme); - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_video_call); - Objects.requireNonNull(getSupportActionBar()).hide(); - initUI(); - requestPermissions(); - } - - @Override - protected void onPause() { - super.onPause(); - - if (mSession == null) { - return; - } - mSession.onPause(); - - if (isFinishing()) { - disconnectSession(); - } - } - - @Override - protected void onResume() { - super.onResume(); - - if (mSession == null) { - return; - } - mSession.onResume(); - } - - @Override - protected void onDestroy() { - disconnectSession(); - cmTimer.stop(); - super.onDestroy(); - } - - @SuppressLint("ClickableViewAccessibility") - private void initUI() { - mPublisherViewContainer = findViewById(R.id.local_video_view_container); - mSubscriberViewContainer = findViewById(R.id.remote_video_view_container); - - apiKey = getIntent().getStringExtra("apiKey"); - sessionId = getIntent().getStringExtra("sessionId"); - token = getIntent().getStringExtra("token"); - appLang = getIntent().getStringExtra("appLang"); - baseUrl = getIntent().getStringExtra("baseUrl"); - sessionStatusModel = getIntent().getParcelableExtra("sessionStatusModel"); - - controlPanel = findViewById(R.id.control_panel); - - videoCallPresenter = new VideoCallPresenterImpl(this, baseUrl); - - patientName = findViewById(R.id.patient_name); - patientName.setText(sessionStatusModel.getPatientName()); - - cmTimer = findViewById(R.id.cmTimer); - cmTimer.setFormat("mm:ss"); - cmTimer.setOnChronometerTickListener(arg0 -> { - long minutes; - long seconds; - if (!resume) { - minutes = ((SystemClock.elapsedRealtime() - cmTimer.getBase()) / 1000) / 60; - seconds = ((SystemClock.elapsedRealtime() - cmTimer.getBase()) / 1000) % 60; - elapsedTime = SystemClock.elapsedRealtime(); - } else { - minutes = ((elapsedTime - cmTimer.getBase()) / 1000) / 60; - seconds = ((elapsedTime - cmTimer.getBase()) / 1000) % 60; - elapsedTime = elapsedTime + 1000; - } - Log.d(TAG, "onChronometerTick: " + minutes + " : " + seconds); - }); - - mCallBtn = findViewById(R.id.btn_call); - btnMinimize = findViewById(R.id.btn_minimize); - mCameraBtn = findViewById(R.id.btn_camera); - mSwitchCameraBtn = findViewById(R.id.btn_switch_camera); - mspeckerBtn = findViewById(R.id.btn_specker); - mMicBtn = findViewById(R.id.btn_mic); - - // progressBarLayout=findViewById(R.id.progressBar); - // progressBar=findViewById(R.id.progress_bar); -// progressBarTextView=findViewById(R.id.progress_bar_text); -// progressBar.setVisibility(View.GONE); - - hiddenButtons(); - - checkClientConnected(); - - mSubscriberViewContainer.setOnTouchListener((v, event) -> { - controlPanel.setVisibility(View.VISIBLE); - mVolHandler.removeCallbacks(mVolRunnable); - mVolHandler.postDelayed(mVolRunnable, 5 * 1000); - return true; - }); - - if (appLang.equals("ar")) { - progressBarLayout.setLayoutDirection(View.LAYOUT_DIRECTION_RTL); - } - - } - - private void checkClientConnected() { - mConnectedHandler = new Handler(); - mConnectedRunnable = () -> { - if (!isConnected) { - videoCallPresenter.callClintConnected(sessionStatusModel); - } - }; - mConnectedHandler.postDelayed(mConnectedRunnable, 55 * 1000); - - } - - private void hiddenButtons() { - mVolHandler = new Handler(); - mVolRunnable = () -> controlPanel.setVisibility(View.GONE); - mVolHandler.postDelayed(mVolRunnable, 5 * 1000); - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - - EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); - } - - @Override - public void onPermissionsGranted(int requestCode, List perms) { - Log.d(TAG, "onPermissionsGranted:" + requestCode + ":" + perms.size()); - } - - @Override - public void onPermissionsDenied(int requestCode, List perms) { - Log.d(TAG, "onPermissionsDenied:" + requestCode + ":" + perms.size()); - - if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) { - new AppSettingsDialog.Builder(this) - .setTitle(getString(R.string.title_settings_dialog)) - .setRationale(getString(R.string.rationale_ask_again)) - .setPositiveButton(getString(R.string.setting)) - .setNegativeButton(getString(R.string.cancel)) - .setRequestCode(RC_SETTINGS_SCREEN_PERM) - .build() - .show(); - } - } - - @AfterPermissionGranted(RC_VIDEO_APP_PERM) - private void requestPermissions() { - String[] perms = {Manifest.permission.INTERNET, Manifest.permission.CAMERA,}; - if (EasyPermissions.hasPermissions(this, perms)) { - try { - mSession = new Session.Builder(this, apiKey, sessionId).build(); - mSession.setSessionListener(this); - mSession.connect(token); - } catch (Exception e) { - e.printStackTrace(); - } - } else { - EasyPermissions.requestPermissions(this, getString(R.string.remaining_ar), RC_VIDEO_APP_PERM, perms); - } - } - - @Override - public void onConnected(Session session) { - Log.i(TAG, "Session Connected"); - - mPublisher = new Publisher.Builder(this).build(); - mPublisher.setPublisherListener(this); - - mPublisherViewContainer.addView(mPublisher.getView()); - - if (mPublisher.getView() instanceof GLSurfaceView) { - ((GLSurfaceView) mPublisher.getView()).setZOrderOnTop(true); - } - - mSession.publish(mPublisher); - - if (!resume) { - cmTimer.setBase(SystemClock.elapsedRealtime()); - } - cmTimer.start(); - } - - @Override - public void onDisconnected(Session session) { - Log.d(TAG, "onDisconnected: disconnected from session " + session.getSessionId()); - - mSession = null; - cmTimer.stop(); - } - - @Override - public void onError(Session session, OpentokError opentokError) { - Log.d(TAG, "onError: Error (" + opentokError.getMessage() + ") in session " + session.getSessionId()); - - // Toast.makeText(this, "Session error. See the logcat please.", Toast.LENGTH_LONG).show(); - //finish(); - } - - @Override - public void onStreamReceived(Session session, Stream stream) { - Log.d(TAG, "onStreamReceived: New stream " + stream.getStreamId() + " in session " + session.getSessionId()); - if (mSubscriber != null) { - isConnected = true; - return; - } - isConnected = true; - subscribeToStream(stream); - if(mConnectedHandler!=null && mConnectedRunnable!=null) - mConnectedHandler.removeCallbacks(mConnectedRunnable); - videoCallPresenter.callChangeCallStatus(new ChangeCallStatusRequestModel(3,sessionStatusModel.getDoctorId(), sessionStatusModel.getGeneralid(),token,sessionStatusModel.getVCID())); - } - - @Override - public void onStreamDropped(Session session, Stream stream) { - Log.d(TAG, "onStreamDropped: Stream " + stream.getStreamId() + " dropped from session " + session.getSessionId()); - - if (mSubscriber == null) { - return; - } - - if (mSubscriber.getStream().equals(stream)) { - mSubscriberViewContainer.removeView(mSubscriber.getView()); - mSubscriber.destroy(); - mSubscriber = null; - } - disconnectSession(); - } - - @Override - public void onStreamCreated(PublisherKit publisherKit, Stream stream) { - Log.d(TAG, "onStreamCreated: Own stream " + stream.getStreamId() + " created"); - } - - @Override - public void onStreamDestroyed(PublisherKit publisherKit, Stream stream) { - Log.d(TAG, "onStreamDestroyed: Own stream " + stream.getStreamId() + " destroyed"); - } - - @Override - public void onError(PublisherKit publisherKit, OpentokError opentokError) { - Log.d(TAG, "onError: Error (" + opentokError.getMessage() + ") in publisher"); - - // Toast.makeText(this, "onError: Error (" + opentokError.getMessage() + ") in publisher", Toast.LENGTH_LONG).show(); - // finish(); - } - - @Override - public void onVideoDataReceived(SubscriberKit subscriberKit) { - mSubscriber.setStyle(BaseVideoRenderer.STYLE_VIDEO_SCALE, BaseVideoRenderer.STYLE_VIDEO_FILL); - mSubscriberViewContainer.addView(mSubscriber.getView()); - } - - @Override - public void onVideoDisabled(SubscriberKit subscriberKit, String s) { - - } - - @Override - public void onVideoEnabled(SubscriberKit subscriberKit, String s) { - - } - - @Override - public void onVideoDisableWarning(SubscriberKit subscriberKit) { - - } - - @Override - public void onVideoDisableWarningLifted(SubscriberKit subscriberKit) { - - } - - private void subscribeToStream(Stream stream) { - mSubscriber = new Subscriber.Builder(VideoCallActivity.this, stream).build(); - mSubscriber.setVideoListener(this); - mSession.subscribe(mSubscriber); - } - - private void disconnectSession() { - if (mSession == null) { - setResult(Activity.RESULT_CANCELED); - finish(); - return; - } - - if (mSubscriber != null) { - mSubscriberViewContainer.removeView(mSubscriber.getView()); - mSession.unsubscribe(mSubscriber); - mSubscriber.destroy(); - mSubscriber = null; - } - - if (mPublisher != null) { - mPublisherViewContainer.removeView(mPublisher.getView()); - mSession.unpublish(mPublisher); - mPublisher.destroy(); - mPublisher = null; - } - mSession.disconnect(); - if (countDownTimer != null) { - countDownTimer.cancel(); - } - videoCallPresenter.callChangeCallStatus(new ChangeCallStatusRequestModel(16,sessionStatusModel.getDoctorId(), sessionStatusModel.getGeneralid(),token,sessionStatusModel.getVCID())); - finish(); - } - - public void onSwitchCameraClicked(View view) { - if (mPublisher != null) { - isSwitchCameraClicked = !isSwitchCameraClicked; - mPublisher.cycleCamera(); - int res = isSwitchCameraClicked ? R.drawable.camera_front : R.drawable.camera_back; - mSwitchCameraBtn.setImageResource(res); - } - } - - public void onCallClicked(View view) { - disconnectSession(); - } - - public void onMinimizedClicked(View view) { - - } - - public void onCameraClicked(View view) { - if (mPublisher != null) { - isCameraClicked = !isCameraClicked; - mPublisher.setPublishVideo(!isCameraClicked); - int res = isCameraClicked ? R.drawable.video_disabled : R.drawable.video_enabled; - mCameraBtn.setImageResource(res); - } - } - - public void onMicClicked(View view) { - - if (mPublisher != null) { - isMicClicked = !isMicClicked; - mPublisher.setPublishAudio(!isMicClicked); - int res = isMicClicked ? R.drawable.mic_disabled : R.drawable.mic_enabled; - mMicBtn.setImageResource(res); - } - } - - public void onSpeckerClicked(View view) { - if (mSubscriber != null) { - isSpeckerClicked = !isSpeckerClicked; - mSubscriber.setSubscribeToAudio(!isSpeckerClicked); - int res = isSpeckerClicked ? R.drawable.audio_disabled : R.drawable.audio_enabled; - mspeckerBtn.setImageResource(res); - } - } - - @Override - public void onCallSuccessful(SessionStatusModel sessionStatusModel) { - if (sessionStatusModel.getSessionStatus() == 2 || sessionStatusModel.getSessionStatus() == 3) { - Intent returnIntent = new Intent(); - returnIntent.putExtra("sessionStatusNotRespond", sessionStatusModel); - setResult(Activity.RESULT_OK, returnIntent); - finish(); - } else if( sessionStatusModel.getSessionStatus() == 4 ){ - isConnected = true; - if(mConnectedHandler!=null && mConnectedRunnable!=null) - mConnectedHandler.removeCallbacks(mConnectedRunnable); - } - } - - @Override - public void onCallChangeCallStatusSuccessful(SessionStatusModel sessionStatusModel) { - - } - - @Override - public void onFailure() { - - } -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallResponseListener.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallResponseListener.kt index eceed01e..204568a4 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallResponseListener.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallResponseListener.kt @@ -9,4 +9,6 @@ interface VideoCallResponseListener { fun errorHandle(message: String) fun minimizeVideoEvent(isMinimize : Boolean) + + fun onBackHandle(){} } \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt index 1dc3d267..f7e6c4b2 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt @@ -16,7 +16,6 @@ import android.view.* import android.widget.* import androidx.annotation.Nullable import androidx.constraintlayout.widget.ConstraintLayout -import androidx.constraintlayout.widget.ConstraintSet import androidx.core.view.GestureDetectorCompat import androidx.fragment.app.DialogFragment import com.hmg.hmgDr.Model.ChangeCallStatusRequestModel @@ -39,7 +38,7 @@ import kotlin.math.ceil class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.SessionListener, PublisherListener, SubscriberKit.VideoListener, VideoCallView { - var isFullScreen: Boolean = true + private var isFullScreen: Boolean = true private var x_init_cord = 0 private var y_init_cord: Int = 0 private var x_init_margin: Int = 0 @@ -48,7 +47,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session private lateinit var mWindowManager: WindowManager private var isLeft = true - lateinit var videoCallPresenter: VideoCallPresenter + private lateinit var videoCallPresenter: VideoCallPresenter private var mSession: Session? = null private var mPublisher: Publisher? = null @@ -88,8 +87,8 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session private lateinit var patientName: TextView private lateinit var cmTimer: Chronometer - var elapsedTime: Long = 0 - var resume = false + private var elapsedTime: Long = 0 + private var resume = false private val progressBar: ProgressBar? = null private val countDownTimer: CountDownTimer? = null @@ -122,6 +121,18 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session override fun onCreateDialog(@Nullable savedInstanceState: Bundle?): Dialog { val dialog: Dialog = super.onCreateDialog(savedInstanceState) + + // Add back button listener + // Add back button listener + dialog.setOnKeyListener { _, keyCode, keyEvent -> + // getAction to make sure this doesn't double fire + if (keyCode == KeyEvent.KEYCODE_BACK && keyEvent.action == KeyEvent.ACTION_UP) { + videoCallResponseListener?.onBackHandle() + false // Capture onKey + } else true + // Don't capture + } + return dialog } @@ -438,7 +449,6 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session dialog?.dismiss() } - override fun onCallSuccessful(sessionStatusModel: SessionStatusModel) { if (sessionStatusModel.sessionStatus == 2 || sessionStatusModel.sessionStatus == 3) { val returnIntent = Intent() @@ -523,7 +533,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session //// localPreviewLayoutParam = RelativeLayout.LayoutParams(localPreviewWidth, localPreviewHeight) localPreviewLayoutParam.width = localPreviewWidth localPreviewLayoutParam.height = localPreviewHeight - localPreviewLayoutParam.setMargins(0,localPreviewMargin, localPreviewMargin, 0) + localPreviewLayoutParam.setMargins(0, localPreviewMargin, localPreviewMargin, 0) // remotePreviewLayoutParam = FrameLayout.LayoutParams(remotePreviewIconSize, remotePreviewIconSize) remotePreviewLayoutParam.width = remotePreviewIconSize remotePreviewLayoutParam.height = remotePreviewIconSize @@ -544,7 +554,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session localPreviewLayoutParam.width = 0 localPreviewLayoutParam.height = 0 - localPreviewLayoutParam.setMargins(0,localPreviewMargin / 2, localPreviewMargin/ 2, 0) + localPreviewLayoutParam.setMargins(0, localPreviewMargin / 2, localPreviewMargin / 2, 0) // localPreviewLayoutIconParam = FrameLayout.LayoutParams(localPreviewIconSmall, localPreviewIconSmall) //// localPreviewLayoutParam = RelativeLayout.LayoutParams(localPreviewWidthSmall, localPreviewHeightSmall) // localPreviewLayoutParam.width = localPreviewWidthSmall @@ -591,7 +601,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session } @SuppressLint("ClickableViewAccessibility") - fun handleDragDialog() { + private fun handleDragDialog() { mWindowManager = requireActivity().getSystemService(Context.WINDOW_SERVICE) as WindowManager getWindowManagerDefaultDisplay() @@ -600,7 +610,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session } @SuppressLint("ClickableViewAccessibility") - val dragListener: View.OnTouchListener = View.OnTouchListener { _, event -> + private val dragListener: View.OnTouchListener = View.OnTouchListener { _, event -> mDetector.onTouchEvent(event) //Get Floating widget view params @@ -631,10 +641,10 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session // y_cord_Destination = 0 // y_cord_Destination = // -(szWindow.y - (videoCallContainer.height /*+ barHeight*/)) - y_cord_Destination = - (szWindow.y/2) + y_cord_Destination = -(szWindow.y / 2) } else if (y_cord_Destination + (videoCallContainer.height + barHeight) > szWindow.y) { // y_cord_Destination = szWindow.y - (videoCallContainer.height + barHeight) - y_cord_Destination = (szWindow.y/2) + y_cord_Destination = (szWindow.y / 2) } layoutParams.y = y_cord_Destination diff --git a/android/app/src/main/res/drawable/circle_shape.xml b/android/app/src/main/res/drawable/circle_shape.xml new file mode 100644 index 00000000..2cb09469 --- /dev/null +++ b/android/app/src/main/res/drawable/circle_shape.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/layout_rounded_bg.xml b/android/app/src/main/res/drawable/layout_rounded_bg.xml index 0c46f49b..ce73e763 100644 --- a/android/app/src/main/res/drawable/layout_rounded_bg.xml +++ b/android/app/src/main/res/drawable/layout_rounded_bg.xml @@ -1,7 +1,13 @@ - - - - + + + + From 746f44157ed9f20db0b991b96b70c6cd4d22f706 Mon Sep 17 00:00:00 2001 From: Mohammad Aljammal Date: Wed, 16 Jun 2021 14:54:10 +0300 Subject: [PATCH 19/30] Add video call service --- .../hmgDr/ui/fragment/VideoCallFragment.kt | 2 + lib/core/service/NavigationService.dart | 10 +- lib/core/service/VideoCallService.dart | 88 +++++++++++++++ lib/landing_page.dart | 3 +- lib/locator.dart | 2 + lib/routes.dart | 3 + lib/screens/live_care/end_call_screen.dart | 44 +++++--- .../patient_profile_screen.dart | 102 ++++++++++-------- .../profile/patient-profile-app-bar.dart | 9 +- lib/widgets/shared/app_scaffold_widget.dart | 8 +- 10 files changed, 203 insertions(+), 68 deletions(-) create mode 100644 lib/core/service/VideoCallService.dart diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt index f7e6c4b2..9a2deab6 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt @@ -370,6 +370,8 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session } isConnected = true subscribeToStream(stream) + if (mConnectedHandler != null && mConnectedRunnable != null) + mConnectedHandler!!.removeCallbacks(mConnectedRunnable!!) videoCallPresenter.callChangeCallStatus(ChangeCallStatusRequestModel(3, sessionStatusModel!!.doctorId, sessionStatusModel!!.generalid, token, sessionStatusModel!!.vcid)) } diff --git a/lib/core/service/NavigationService.dart b/lib/core/service/NavigationService.dart index 426ace4d..26191ffc 100644 --- a/lib/core/service/NavigationService.dart +++ b/lib/core/service/NavigationService.dart @@ -3,9 +3,15 @@ import 'package:flutter/material.dart'; class NavigationService { final GlobalKey navigatorKey = new GlobalKey(); - Future navigateTo(String routeName) { - return navigatorKey.currentState.pushNamed(routeName); + Future navigateTo(String routeName,{Object arguments}) { + return navigatorKey.currentState.pushNamed(routeName,arguments: arguments); } + + Future pushReplacementNamed(String routeName,{Object arguments}) { + return navigatorKey.currentState.pushReplacementNamed(routeName,arguments: arguments); + } + + Future pushNamedAndRemoveUntil(String routeName) { return navigatorKey.currentState.pushNamedAndRemoveUntil(routeName,(asd)=>false); } diff --git a/lib/core/service/VideoCallService.dart b/lib/core/service/VideoCallService.dart new file mode 100644 index 00000000..af832aad --- /dev/null +++ b/lib/core/service/VideoCallService.dart @@ -0,0 +1,88 @@ +import 'package:doctor_app_flutter/config/config.dart'; +import 'package:doctor_app_flutter/config/shared_pref_kay.dart'; +import 'package:doctor_app_flutter/core/service/base/base_service.dart'; +import 'package:doctor_app_flutter/core/service/patient/LiveCarePatientServices.dart'; +import 'package:doctor_app_flutter/models/doctor/doctor_profile_model.dart'; +import 'package:doctor_app_flutter/models/livecare/end_call_req.dart'; +import 'package:doctor_app_flutter/models/livecare/session_status_model.dart'; +import 'package:doctor_app_flutter/models/livecare/start_call_res.dart'; +import 'package:doctor_app_flutter/models/patient/patiant_info_model.dart'; +import 'package:doctor_app_flutter/util/VideoChannel.dart'; +import 'package:doctor_app_flutter/util/dr_app_toast_msg.dart'; +import 'package:doctor_app_flutter/widgets/shared/loader/gif_loader_dialog_utils.dart'; +import 'package:flutter/cupertino.dart'; + +import '../../locator.dart'; +import '../../routes.dart'; +import 'NavigationService.dart'; + +class VideoCallService extends BaseService{ + + StartCallRes startCallRes; + PatiantInformtion patient; + LiveCarePatientServices _liveCarePatientServices = locator(); + + openVideo(StartCallRes startModel,PatiantInformtion patientModel,VoidCallback onCallConnected, VoidCallback onCallDisconnected)async{ + this.startCallRes = startModel; + this.patient = patientModel; + DoctorProfileModel doctorProfile = await getDoctorProfile(isGetProfile: true); + await VideoChannel.openVideoCallScreen( + kToken: "T1==cGFydG5lcl9pZD00NzI0Nzk1NCZzaWc9NGIyZDljOTY3YjFiNWU1YzUzNzFmMjIyNjJmNmEzY2Y5NzZjOTdlYzpzZXNzaW9uX2lkPTFfTVg0ME56STBOemsxTkg1LU1UWXlNekEyTlRRMU9EVXhObjVrVFRoMFlVdFJXaXRYTWpadFZGZHFhSGxZVGpOdE1UVi1mZyZjcmVhdGVfdGltZT0xNjIzMDY1NDk1Jm5vbmNlPTAuMjM2Mjk0NTIwMTkyOTA4OTcmcm9sZT1wdWJsaXNoZXImZXhwaXJlX3RpbWU9MTYyNTY1NzQ5NCZpbml0aWFsX2xheW91dF9jbGFzc19saXN0PQ==",//startCallRes.openTokenID, + kSessionId: "1_MX40NzI0Nzk1NH5-MTYyMzA2NTQ1ODUxNn5kTTh0YUtRWitXMjZtVFdqaHlYTjNtMTV-fg",//startCallRes.openSessionID, + kApiKey: '47247954',//'46209962', + vcId: patient.vcId, + patientName: patient.fullName ?? (patient.firstName != null ? "${patient.firstName} ${patient.lastName}" : "-"), + tokenID: await sharedPref.getString(TOKEN), + generalId: GENERAL_ID, + doctorId: doctorProfile.doctorID, + onFailure: (String error) { + DrAppToastMsg.showErrorToast(error); + },onCallConnected: onCallConnected, + onCallEnd: () { + WidgetsBinding.instance.addPostFrameCallback((_) async { + GifLoaderDialogUtils.showMyDialog(locator().navigatorKey.currentContext); + endCall(patient.vcId, false,).then((value) { + GifLoaderDialogUtils.hideDialog(locator().navigatorKey.currentContext); + if (hasError) { + DrAppToastMsg.showErrorToast(error); + }else + locator().navigateTo(PATIENTS_END_Call,arguments: { + "patient": patient, + }); + + }); + }); + }, + onCallNotRespond: (SessionStatusModel sessionStatusModel) { + WidgetsBinding.instance.addPostFrameCallback((_) { + GifLoaderDialogUtils.showMyDialog(locator().navigatorKey.currentContext); + endCall(patient.vcId, sessionStatusModel.sessionStatus == 3,).then((value) { + GifLoaderDialogUtils.hideDialog(locator().navigatorKey.currentContext); + if (hasError) { + DrAppToastMsg.showErrorToast(error); + } else { + locator().navigateTo(PATIENTS_END_Call,arguments: { + "patient": patient, + }); + } + + }); + + }); + }); + } + Future endCall(int vCID, bool isPatient) async { + hasError = false; + // await getDoctorProfile(isGetProfile: true); + EndCallReq endCallReq = new EndCallReq(); + endCallReq.doctorId = doctorProfile.doctorID; + endCallReq.generalid = 'Cs2020@2016\$2958'; + endCallReq.vCID = vCID; + endCallReq.isDestroy = isPatient; + //await _liveCarePatientServices.endCall(endCallReq); + if (_liveCarePatientServices.hasError) { + error = _liveCarePatientServices.error; + } + } + +} \ No newline at end of file diff --git a/lib/landing_page.dart b/lib/landing_page.dart index cd7a8171..5bce4f71 100644 --- a/lib/landing_page.dart +++ b/lib/landing_page.dart @@ -4,6 +4,7 @@ import 'package:doctor_app_flutter/screens/home/home_screen.dart'; import 'package:doctor_app_flutter/screens/qr_reader/QR_reader_screen.dart'; import 'package:doctor_app_flutter/util/translations_delegate_base.dart'; import 'package:doctor_app_flutter/widgets/shared/app_drawer_widget.dart'; +import 'package:doctor_app_flutter/widgets/shared/app_scaffold_widget.dart'; import 'package:doctor_app_flutter/widgets/shared/bottom_nav_bar.dart'; import 'package:doctor_app_flutter/widgets/shared/user-guid/app_showcase_widget.dart'; import 'package:flutter/cupertino.dart'; @@ -33,7 +34,7 @@ class _LandingPageState extends State { @override Widget build(BuildContext context) { - return Scaffold( + return AppScaffold( appBar: currentTab != 0 ? AppBar( elevation: 0, diff --git a/lib/locator.dart b/lib/locator.dart index 1e4bcb4c..d74fad8b 100644 --- a/lib/locator.dart +++ b/lib/locator.dart @@ -11,6 +11,7 @@ import 'package:doctor_app_flutter/core/viewModel/sick_leave_view_model.dart'; import 'package:get_it/get_it.dart'; import 'core/service/NavigationService.dart'; +import 'core/service/VideoCallService.dart'; import 'core/service/home/dasboard_service.dart'; import 'core/service/home/doctor_reply_service.dart'; import 'core/service/home/schedule_service.dart'; @@ -96,6 +97,7 @@ void setupLocator() { locator.registerLazySingleton(() => NavigationService()); locator.registerLazySingleton(() => ScanQrService()); locator.registerLazySingleton(() => SpecialClinicsService()); + locator.registerLazySingleton(() => VideoCallService()); /// View Model locator.registerFactory(() => DoctorReplayViewModel()); diff --git a/lib/routes.dart b/lib/routes.dart index 33aa5636..0826c76e 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -1,4 +1,5 @@ import 'package:doctor_app_flutter/root_page.dart'; +import 'package:doctor_app_flutter/screens/live_care/end_call_screen.dart'; import 'package:doctor_app_flutter/screens/medical-file/health_summary_page.dart'; import 'package:doctor_app_flutter/screens/patients/ECGPage.dart'; import 'package:doctor_app_flutter/screens/patients/insurance_approval_screen_patient.dart'; @@ -37,6 +38,7 @@ const String LOGIN = 'login'; const String VERIFICATION_METHODS = 'verification-methods'; const String PATIENTS = 'patients/patients'; const String PATIENTS_PROFILE = 'patients/patients-profile'; +const String PATIENTS_END_Call = 'patients/patients-profile/endCall'; const String IN_PATIENTS_PROFILE = 'inpatients/patients-profile'; const String LAB_RESULT = 'patients/lab_result'; const String HEALTH_SUMMARY = 'patients/health-summary'; @@ -88,6 +90,7 @@ var routes = { PATIENT_MEDICAL_REPORT: (_) => MedicalReportPage(), PATIENT_MEDICAL_REPORT_INSERT: (_) => AddVerifyMedicalReport(), PATIENT_MEDICAL_REPORT_DETAIL: (_) => MedicalReportDetailPage(), + PATIENTS_END_Call: (_) => EndCallScreen(), CREATE_EPISODE: (_) => UpdateSoapIndex( isUpdate: true, ), diff --git a/lib/screens/live_care/end_call_screen.dart b/lib/screens/live_care/end_call_screen.dart index 76e87a87..d355473d 100644 --- a/lib/screens/live_care/end_call_screen.dart +++ b/lib/screens/live_care/end_call_screen.dart @@ -23,9 +23,9 @@ import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:hexcolor/hexcolor.dart'; class EndCallScreen extends StatefulWidget { - final PatiantInformtion patient; - const EndCallScreen({Key key, this.patient}) : super(key: key); + + const EndCallScreen({Key key,}) : super(key: key); @override _EndCallScreenState createState() => _EndCallScreenState(); @@ -33,7 +33,7 @@ class EndCallScreen extends StatefulWidget { class _EndCallScreenState extends State { bool isInpatient = false; - + PatiantInformtion patient; bool isDischargedPatient = false; bool isSearchAndOut = false; String patientType; @@ -43,6 +43,13 @@ class _EndCallScreenState extends State { LiveCarePatientViewModel liveCareModel; + @override + void didChangeDependencies() { + super.didChangeDependencies(); + final routeArgs = ModalRoute.of(context).settings.arguments as Map; + patient = routeArgs['patient']; + } + @override Widget build(BuildContext context) { final List cardsList = [ @@ -53,7 +60,7 @@ class _EndCallScreenState extends State { onTap: () async { GifLoaderDialogUtils.showMyDialog(context); await liveCareModel - .startCall(isReCall: false, vCID: widget.patient.vcId) + .startCall(isReCall: false, vCID: patient.vcId) .then((value) async { await liveCareModel.getDoctorProfile(); GifLoaderDialogUtils.hideDialog(context); @@ -64,8 +71,8 @@ class _EndCallScreenState extends State { kToken: liveCareModel.startCallRes.openTokenID, kSessionId: liveCareModel.startCallRes.openSessionID, kApiKey: '46209962', - vcId: widget.patient.vcId, - patientName: widget.patient.fullName ?? (widget.patient.firstName != null ? "${widget.patient.firstName} ${widget.patient.lastName}" : "-"), + vcId: patient.vcId, + patientName: patient.fullName ?? (patient.firstName != null ? "${patient.firstName} ${patient.lastName}" : "-"), tokenID: await liveCareModel.getToken(), generalId: GENERAL_ID, doctorId: liveCareModel.doctorProfile.doctorID, @@ -76,7 +83,7 @@ class _EndCallScreenState extends State { GifLoaderDialogUtils.showMyDialog(context); GifLoaderDialogUtils.showMyDialog(context); await liveCareModel.endCall( - widget.patient.vcId, + patient.vcId, false, ); GifLoaderDialogUtils.hideDialog(context); @@ -89,7 +96,7 @@ class _EndCallScreenState extends State { GifLoaderDialogUtils.showMyDialog(context); GifLoaderDialogUtils.showMyDialog(context); await liveCareModel.endCall( - widget.patient.vcId, + patient.vcId, sessionStatusModel.sessionStatus == 3, ); GifLoaderDialogUtils.hideDialog(context); @@ -116,14 +123,14 @@ class _EndCallScreenState extends State { () async { Navigator.of(context).pop(); GifLoaderDialogUtils.showMyDialog(context); - await liveCareModel.getAlternativeServices(widget.patient.vcId); + await liveCareModel.getAlternativeServices(patient.vcId); GifLoaderDialogUtils.hideDialog(context); if (liveCareModel.state == ViewState.ErrorLocal) { DrAppToastMsg.showErrorToast(liveCareModel.error); } else { showAlternativesDialog(context, liveCareModel, (bool isConfirmed) async { GifLoaderDialogUtils.showMyDialog(context); - await liveCareModel.endCallWithCharge(widget.patient.vcId, isConfirmed); + await liveCareModel.endCallWithCharge(patient.vcId, isConfirmed); GifLoaderDialogUtils.hideDialog(context); if (liveCareModel.state == ViewState.ErrorLocal) { DrAppToastMsg.showErrorToast(liveCareModel.error); @@ -147,7 +154,7 @@ class _EndCallScreenState extends State { () async { Navigator.of(context).pop(); GifLoaderDialogUtils.showMyDialog(context); - await liveCareModel.sendSMSInstruction(widget.patient.vcId); + await liveCareModel.sendSMSInstruction(patient.vcId); GifLoaderDialogUtils.hideDialog(context); if (liveCareModel.state == ViewState.ErrorLocal) { DrAppToastMsg.showErrorToast(liveCareModel.error); @@ -169,7 +176,7 @@ class _EndCallScreenState extends State { context, MaterialPageRoute( builder: (BuildContext context) => - LivaCareTransferToAdmin(patient: widget.patient))); + LivaCareTransferToAdmin(patient: patient))); }, isInPatient: isInpatient, isDartIcon: true, @@ -186,10 +193,14 @@ class _EndCallScreenState extends State { backgroundColor: Theme.of(context).scaffoldBackgroundColor, isShowAppBar: true, appBar: PatientProfileAppBar( - widget.patient, + patient, + onPressed: (){ + Navigator.pop(context); + + }, isInpatient: isInpatient, - height: (widget.patient.patientStatusType != null && - widget.patient.patientStatusType == 43) + height: (patient.patientStatusType != null && + patient.patientStatusType == 43) ? 210 : isDischargedPatient ? 240 @@ -232,7 +243,7 @@ class _EndCallScreenState extends State { staggeredTileBuilder: (int index) => StaggeredTile.fit(1), itemBuilder: (BuildContext context, int index) => PatientProfileButton( - patient: widget.patient, + patient: patient, patientType: patientType, arrivalType: arrivalType, from: from, @@ -331,6 +342,7 @@ class _EndCallScreenState extends State { ), AppButton( onPressed: () { + Navigator.of(context).pop(); Navigator.of(context).pop(); okFunction(false); }, 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 d755af4a..9aa95d16 100644 --- a/lib/screens/patients/profile/profile_screen/patient_profile_screen.dart +++ b/lib/screens/patients/profile/profile_screen/patient_profile_screen.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:doctor_app_flutter/config/config.dart'; import 'package:doctor_app_flutter/core/enum/viewstate.dart'; +import 'package:doctor_app_flutter/core/service/VideoCallService.dart'; import 'package:doctor_app_flutter/core/viewModel/LiveCarePatientViewModel.dart'; import 'package:doctor_app_flutter/core/viewModel/SOAP_view_model.dart'; import 'package:doctor_app_flutter/models/SOAP/PostEpisodeReqModel.dart'; @@ -25,6 +26,7 @@ import 'package:flutter/material.dart'; import 'package:hexcolor/hexcolor.dart'; import 'package:quiver/async.dart'; +import '../../../../locator.dart'; import '../../../../routes.dart'; class PatientProfileScreen extends StatefulWidget { @@ -91,6 +93,9 @@ class _PatientProfileScreenState extends State if(routeArgs.containsKey("isFromLiveCare")) { isFromLiveCare = routeArgs['isFromLiveCare']; } + if(routeArgs.containsKey("isCallFinished")) { + isCallFinished = routeArgs['isCallFinished']; + } if (isInpatient) _activeTab = 0; else @@ -333,9 +338,9 @@ class _PatientProfileScreenState extends State if(isCallFinished) { - Navigator.push(context, MaterialPageRoute( - builder: (BuildContext context) => - EndCallScreen(patient:patient))); + // Navigator.push(context, MaterialPageRoute( + // builder: (BuildContext context) => EndCallScreen(patient:patient))); + var asd = ""; } else { GifLoaderDialogUtils.showMyDialog(context); // await model.startCall( isReCall : false, vCID: patient.vcId); @@ -349,49 +354,54 @@ class _PatientProfileScreenState extends State patient.episodeNo = 0; GifLoaderDialogUtils.hideDialog(context); - await VideoChannel.openVideoCallScreen( - kToken: "T1==cGFydG5lcl9pZD00NzI0Nzk1NCZzaWc9NGIyZDljOTY3YjFiNWU1YzUzNzFmMjIyNjJmNmEzY2Y5NzZjOTdlYzpzZXNzaW9uX2lkPTFfTVg0ME56STBOemsxTkg1LU1UWXlNekEyTlRRMU9EVXhObjVrVFRoMFlVdFJXaXRYTWpadFZGZHFhSGxZVGpOdE1UVi1mZyZjcmVhdGVfdGltZT0xNjIzMDY1NDk1Jm5vbmNlPTAuMjM2Mjk0NTIwMTkyOTA4OTcmcm9sZT1wdWJsaXNoZXImZXhwaXJlX3RpbWU9MTYyNTY1NzQ5NCZpbml0aWFsX2xheW91dF9jbGFzc19saXN0PQ==",//model.startCallRes.openTokenID, - kSessionId:"1_MX40NzI0Nzk1NH5-MTYyMzA2NTQ1ODUxNn5kTTh0YUtRWitXMjZtVFdqaHlYTjNtMTV-fg",// model.startCallRes.openSessionID, - kApiKey: '47247954',//46209962 - vcId: patient.vcId, - patientName: patient.fullName ?? (patient.firstName != null ? "${patient.firstName} ${patient.lastName}" : "-"), - tokenID: await model.getToken(), - generalId: GENERAL_ID, - doctorId: model.doctorProfile.doctorID, - onFailure: (String error) { - DrAppToastMsg.showErrorToast(error); - },onCallConnected: callConnected, - onCallEnd: () { - var asd=""; - // WidgetsBinding.instance.addPostFrameCallback((_) { - // GifLoaderDialogUtils.showMyDialog(context); - // model.endCall(patient.vcId, false,).then((value) { - // GifLoaderDialogUtils.hideDialog(context); - // if (model.state == ViewState.ErrorLocal) { - // DrAppToastMsg.showErrorToast(model.error); - // } - // setState(() { - // isCallFinished = true; - // }); - // }); - // }); - }, - onCallNotRespond: (SessionStatusModel sessionStatusModel) { - var asd=""; - // WidgetsBinding.instance.addPostFrameCallback((_) { - // GifLoaderDialogUtils.showMyDialog(context); - // model.endCall(patient.vcId, sessionStatusModel.sessionStatus == 3,).then((value) { - // GifLoaderDialogUtils.hideDialog(context); - // if (model.state == ViewState.ErrorLocal) { - // DrAppToastMsg.showErrorToast(model.error); - // } - // setState(() { - // isCallFinished = true; - // }); - // }); - // - // }); - }); + locator().openVideo(model.startCallRes, patient, callConnected, callDisconnected); + + // await VideoChannel.openVideoCallScreen( + // kToken: "T1==cGFydG5lcl9pZD00NzI0Nzk1NCZzaWc9NGIyZDljOTY3YjFiNWU1YzUzNzFmMjIyNjJmNmEzY2Y5NzZjOTdlYzpzZXNzaW9uX2lkPTFfTVg0ME56STBOemsxTkg1LU1UWXlNekEyTlRRMU9EVXhObjVrVFRoMFlVdFJXaXRYTWpadFZGZHFhSGxZVGpOdE1UVi1mZyZjcmVhdGVfdGltZT0xNjIzMDY1NDk1Jm5vbmNlPTAuMjM2Mjk0NTIwMTkyOTA4OTcmcm9sZT1wdWJsaXNoZXImZXhwaXJlX3RpbWU9MTYyNTY1NzQ5NCZpbml0aWFsX2xheW91dF9jbGFzc19saXN0PQ==",//model.startCallRes.openTokenID, + // kSessionId:"1_MX40NzI0Nzk1NH5-MTYyMzA2NTQ1ODUxNn5kTTh0YUtRWitXMjZtVFdqaHlYTjNtMTV-fg",// model.startCallRes.openSessionID, + // kApiKey: '47247954',//46209962 + // vcId: patient.vcId, + // patientName: patient.fullName ?? (patient.firstName != null ? "${patient.firstName} ${patient.lastName}" : "-"), + // tokenID: await model.getToken(), + // generalId: GENERAL_ID, + // doctorId: model.doctorProfile.doctorID, + // onFailure: (String error) { + // DrAppToastMsg.showErrorToast(error); + // },onCallConnected: callConnected, + // onCallEnd: () { + // var asd=""; + // WidgetsBinding.instance.addPostFrameCallback((_) { + // GifLoaderDialogUtils.showMyDialog(context); + // model.endCall(patient.vcId, false,).then((value) { + // GifLoaderDialogUtils.hideDialog(context); + // if (model.state == ViewState.ErrorLocal) { + // DrAppToastMsg.showErrorToast(model.error); + // } + // setState(() { + // isCallFinished = true; + // }); + // }); + // }); + // Navigator.push(context, MaterialPageRoute( + // builder: (BuildContext context) => + // EndCallScreen(patient:patient))); + // }, + // onCallNotRespond: (SessionStatusModel sessionStatusModel) { + // var asd=""; + // // WidgetsBinding.instance.addPostFrameCallback((_) { + // // GifLoaderDialogUtils.showMyDialog(context); + // // model.endCall(patient.vcId, sessionStatusModel.sessionStatus == 3,).then((value) { + // // GifLoaderDialogUtils.hideDialog(context); + // // if (model.state == ViewState.ErrorLocal) { + // // DrAppToastMsg.showErrorToast(model.error); + // // } + // // setState(() { + // // isCallFinished = true; + // // }); + // // }); + // // + // // }); + // }); } } diff --git a/lib/widgets/patients/profile/patient-profile-app-bar.dart b/lib/widgets/patients/profile/patient-profile-app-bar.dart index 5cfdc1b6..de374f04 100644 --- a/lib/widgets/patients/profile/patient-profile-app-bar.dart +++ b/lib/widgets/patients/profile/patient-profile-app-bar.dart @@ -33,6 +33,7 @@ class PatientProfileAppBar extends StatelessWidget final String clinic; final bool isAppointmentHeader; final bool isFromLabResult; + final VoidCallback onPressed; PatientProfileAppBar( this.patient, @@ -52,7 +53,7 @@ class PatientProfileAppBar extends StatelessWidget this.episode, this.visitDate, this.isAppointmentHeader = false, - this.isFromLabResult = false}); + this.isFromLabResult = false, this.onPressed}); @override @@ -93,7 +94,11 @@ class PatientProfileAppBar extends StatelessWidget IconButton( icon: Icon(Icons.arrow_back_ios), color: Color(0xFF2B353E), //Colors.black, - onPressed: () => Navigator.pop(context), + onPressed: () { + if(onPressed!=null) + onPressed(); + Navigator.pop(context); + }, ), Expanded( child: AppText( diff --git a/lib/widgets/shared/app_scaffold_widget.dart b/lib/widgets/shared/app_scaffold_widget.dart index e957b5d4..b930c21a 100644 --- a/lib/widgets/shared/app_scaffold_widget.dart +++ b/lib/widgets/shared/app_scaffold_widget.dart @@ -19,8 +19,11 @@ class AppScaffold extends StatelessWidget { final Widget bottomSheet; final Color backgroundColor; final Widget appBar; + final Widget drawer; + final Widget bottomNavigationBar; final String subtitle; final bool isHomeIcon; + final bool extendBody; AppScaffold( {this.appBarTitle = '', this.body, @@ -30,7 +33,7 @@ class AppScaffold extends StatelessWidget { this.bottomSheet, this.backgroundColor, this.isHomeIcon = true, - this.appBar, this.subtitle}); + this.appBar, this.subtitle, this.drawer, this.extendBody = false, this.bottomNavigationBar}); @override Widget build(BuildContext context) { @@ -42,6 +45,9 @@ class AppScaffold extends StatelessWidget { }, child: Scaffold( backgroundColor: backgroundColor ?? Colors.white, + drawer: drawer, + extendBody: extendBody, + bottomNavigationBar: bottomNavigationBar, appBar: isShowAppBar ? appBar ?? AppBar( From 7614b4aa5c7406da5c3c209f6057e65c04ca8661 Mon Sep 17 00:00:00 2001 From: Mohammad Aljammal Date: Wed, 16 Jun 2021 15:12:25 +0300 Subject: [PATCH 20/30] fix the video call issues --- lib/config/config.dart | 4 ++-- lib/core/service/VideoCallService.dart | 10 +++++----- lib/core/viewModel/authentication_view_model.dart | 2 +- lib/screens/home/home_screen.dart | 2 +- lib/screens/patients/PatientsInPatientScreen.dart | 13 ++++++------- 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/lib/config/config.dart b/lib/config/config.dart index eb361798..8241b36c 100644 --- a/lib/config/config.dart +++ b/lib/config/config.dart @@ -5,8 +5,8 @@ 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 = 'https://hmgwebservices.com/'; -const BASE_URL = 'https://uat.hmgwebservices.com/'; +const BASE_URL = 'https://hmgwebservices.com/'; +// const BASE_URL = 'https://uat.hmgwebservices.com/'; const PHARMACY_ITEMS_URL = "Services/Lists.svc/REST/GetPharmcyItems_Region_enh"; const PHARMACY_LIST_URL = "Services/Patients.svc/REST/GetPharmcyList"; const PATIENT_PROGRESS_NOTE_URL = "Services/DoctorApplication.svc/REST/GetProgressNoteForInPatient"; diff --git a/lib/core/service/VideoCallService.dart b/lib/core/service/VideoCallService.dart index af832aad..0ae61a55 100644 --- a/lib/core/service/VideoCallService.dart +++ b/lib/core/service/VideoCallService.dart @@ -27,9 +27,9 @@ class VideoCallService extends BaseService{ this.patient = patientModel; DoctorProfileModel doctorProfile = await getDoctorProfile(isGetProfile: true); await VideoChannel.openVideoCallScreen( - kToken: "T1==cGFydG5lcl9pZD00NzI0Nzk1NCZzaWc9NGIyZDljOTY3YjFiNWU1YzUzNzFmMjIyNjJmNmEzY2Y5NzZjOTdlYzpzZXNzaW9uX2lkPTFfTVg0ME56STBOemsxTkg1LU1UWXlNekEyTlRRMU9EVXhObjVrVFRoMFlVdFJXaXRYTWpadFZGZHFhSGxZVGpOdE1UVi1mZyZjcmVhdGVfdGltZT0xNjIzMDY1NDk1Jm5vbmNlPTAuMjM2Mjk0NTIwMTkyOTA4OTcmcm9sZT1wdWJsaXNoZXImZXhwaXJlX3RpbWU9MTYyNTY1NzQ5NCZpbml0aWFsX2xheW91dF9jbGFzc19saXN0PQ==",//startCallRes.openTokenID, - kSessionId: "1_MX40NzI0Nzk1NH5-MTYyMzA2NTQ1ODUxNn5kTTh0YUtRWitXMjZtVFdqaHlYTjNtMTV-fg",//startCallRes.openSessionID, - kApiKey: '47247954',//'46209962', + kToken: startCallRes.openTokenID, + kSessionId: startCallRes.openSessionID, + kApiKey: '46209962',//'46209962', vcId: patient.vcId, patientName: patient.fullName ?? (patient.firstName != null ? "${patient.firstName} ${patient.lastName}" : "-"), tokenID: await sharedPref.getString(TOKEN), @@ -73,13 +73,13 @@ class VideoCallService extends BaseService{ } Future endCall(int vCID, bool isPatient) async { hasError = false; - // await getDoctorProfile(isGetProfile: true); + await getDoctorProfile(isGetProfile: true); EndCallReq endCallReq = new EndCallReq(); endCallReq.doctorId = doctorProfile.doctorID; endCallReq.generalid = 'Cs2020@2016\$2958'; endCallReq.vCID = vCID; endCallReq.isDestroy = isPatient; - //await _liveCarePatientServices.endCall(endCallReq); + await _liveCarePatientServices.endCall(endCallReq); if (_liveCarePatientServices.hasError) { error = _liveCarePatientServices.error; } diff --git a/lib/core/viewModel/authentication_view_model.dart b/lib/core/viewModel/authentication_view_model.dart index 189dea42..9da1933a 100644 --- a/lib/core/viewModel/authentication_view_model.dart +++ b/lib/core/viewModel/authentication_view_model.dart @@ -255,7 +255,7 @@ class AuthenticationViewModel extends BaseViewModel { /// add  token to shared preferences in case of send activation code is success setDataAfterSendActivationSuccess(SendActivationCodeForDoctorAppResponseModel sendActivationCodeForDoctorAppResponseModel) { print("VerificationCode : " + sendActivationCodeForDoctorAppResponseModel.verificationCode); - DrAppToastMsg.showSuccesToast("VerificationCode : " + sendActivationCodeForDoctorAppResponseModel.verificationCode); + // DrAppToastMsg.showSuccesToast("VerificationCode : " + sendActivationCodeForDoctorAppResponseModel.verificationCode); sharedPref.setString(VIDA_AUTH_TOKEN_ID, sendActivationCodeForDoctorAppResponseModel.vidaAuthTokenID); sharedPref.setString(VIDA_REFRESH_TOKEN_ID, diff --git a/lib/screens/home/home_screen.dart b/lib/screens/home/home_screen.dart index aaf4d9b9..d448083f 100644 --- a/lib/screens/home/home_screen.dart +++ b/lib/screens/home/home_screen.dart @@ -69,7 +69,7 @@ class _HomeScreenState extends State { await model.getDashboard(); await model.getDoctorProfile(isGetProfile: true); await model.checkDoctorHasLiveCare(); - await model.getSpecialClinicalCareList(); + // await model.getSpecialClinicalCareList(); }, builder: (_, model, w) => AppScaffold( baseViewModel: model, diff --git a/lib/screens/patients/PatientsInPatientScreen.dart b/lib/screens/patients/PatientsInPatientScreen.dart index 4971b636..ae0fbb59 100644 --- a/lib/screens/patients/PatientsInPatientScreen.dart +++ b/lib/screens/patients/PatientsInPatientScreen.dart @@ -65,13 +65,12 @@ class _PatientInPatientScreenState extends State return BaseView( onModelReady: (model) async { model.clearPatientList(); - if (widget.specialClinic != null) { - await model - .getSpecialClinicalCareMappingList(widget.specialClinic.clinicID); - requestModel.nursingStationID = - model.specialClinicalCareMappingList[0].nursingStationID; - requestModel.clinicID = 0; - } + // if (widget.specialClinic != null) { + // await model.getSpecialClinicalCareMappingList(widget.specialClinic.clinicID); + // requestModel.nursingStationID = + // model.specialClinicalCareMappingList[0].nursingStationID; + // requestModel.clinicID = 0; + // } model.getInPatientList(requestModel); }, builder: (_, model, w) => From edca552f789024cae097bb67b91056776225459e Mon Sep 17 00:00:00 2001 From: mosazaid Date: Wed, 16 Jun 2021 17:18:12 +0300 Subject: [PATCH 21/30] video fix bugs --- .../hmgDr/ui/fragment/VideoCallFragment.kt | 62 ++++++++++++++++++- .../src/main/res/drawable/circle_shape.xml | 6 +- android/app/src/main/res/drawable/ic_mini.xml | 5 ++ .../main/res/layout/activity_video_call.xml | 22 ++++++- android/app/src/main/res/values/colors.xml | 4 ++ lib/config/config.dart | 4 +- lib/core/service/VideoCallService.dart | 6 +- 7 files changed, 98 insertions(+), 11 deletions(-) create mode 100644 android/app/src/main/res/drawable/ic_mini.xml diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt index 9a2deab6..68db00fb 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt @@ -16,6 +16,7 @@ import android.view.* import android.widget.* import androidx.annotation.Nullable import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.ContextCompat import androidx.core.view.GestureDetectorCompat import androidx.fragment.app.DialogFragment import com.hmg.hmgDr.Model.ChangeCallStatusRequestModel @@ -39,6 +40,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session SubscriberKit.VideoListener, VideoCallView { private var isFullScreen: Boolean = true + private var isCircle: Boolean = false private var x_init_cord = 0 private var y_init_cord: Int = 0 private var x_init_margin: Int = 0 @@ -78,6 +80,8 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session private lateinit var parentView: View private lateinit var videoCallContainer: ConstraintLayout private lateinit var layoutName: RelativeLayout + private lateinit var layoutMini: RelativeLayout + private lateinit var icMini: ImageButton private lateinit var mCallBtn: ImageView private lateinit var btnMinimize: ImageView private lateinit var mCameraBtn: ImageView @@ -179,7 +183,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session requestPermissions() handleDragDialog() - mDetector = GestureDetectorCompat(context, MyGestureListener { showControlPanelTemporarily() }) + mDetector = GestureDetectorCompat(context, MyGestureListener({ showControlPanelTemporarily() }, { miniCircleDoubleTap() })) return parentView } @@ -214,6 +218,8 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session private fun initUI(view: View) { videoCallContainer = view.findViewById(R.id.video_call_ll) layoutName = view.findViewById(R.id.layout_name) + layoutMini = view.findViewById(R.id.layout_mini) + icMini = view.findViewById(R.id.ic_mini) mPublisherViewContainer = view.findViewById(R.id.local_video_view_container) mPublisherViewIcon = view.findViewById(R.id.local_video_view_icon) mSubscriberViewIcon = view.findViewById(R.id.remote_video_view_icon) @@ -240,6 +246,10 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session Log.d(VideoCallFragment.TAG, "onChronometerTick: $minutes : $seconds") } + icMini.setOnClickListener { + onMiniCircleClicked() + } + controlPanel = view.findViewById(R.id.control_panel) videoCallPresenter = VideoCallPresenterImpl(this, baseUrl) mCallBtn = view.findViewById(R.id.btn_call) @@ -478,6 +488,39 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session disconnectSession() } + private fun miniCircleDoubleTap(){ + if (isCircle){ + onMiniCircleClicked() + } + } + + private fun onMiniCircleClicked(){ + if (isCircle) { + dialog?.window?.setLayout( + 400, + 600 + ) + videoCallContainer.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.text_color)) + mSubscriberViewContainer.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.remoteBackground)) + } else { + dialog?.window?.setLayout( + 200, + 200 + ) + videoCallContainer.background = ContextCompat.getDrawable(requireContext(), R.drawable.circle_shape) + mSubscriberViewContainer.background = ContextCompat.getDrawable(requireContext(), R.drawable.circle_shape) + } + isCircle = !isCircle + + if(isCircle){ + controlPanel?.visibility = View.GONE + layoutMini.visibility = View.GONE + } else { + controlPanel?.visibility = View.VISIBLE + layoutMini.visibility = View.VISIBLE + } + } + private fun onMinimizedClicked(view: View?) { if (isFullScreen) { dialog?.window?.setLayout( @@ -522,6 +565,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session if (isFullScreen) { layoutName.visibility = View.VISIBLE + layoutMini.visibility = View.GONE mCameraBtn.visibility = View.VISIBLE mSwitchCameraBtn.visibility = View.VISIBLE // mspeckerBtn.visibility = View.VISIBLE @@ -541,6 +585,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session remotePreviewLayoutParam.height = remotePreviewIconSize } else { layoutName.visibility = View.GONE + layoutMini.visibility = View.VISIBLE mCameraBtn.visibility = View.GONE mSwitchCameraBtn.visibility = View.GONE // mspeckerBtn.visibility = View.GONE @@ -565,6 +610,14 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session // remotePreviewLayoutParam = FrameLayout.LayoutParams(remotePreviewIconSizeSmall, remotePreviewIconSizeSmall) remotePreviewLayoutParam.width = remotePreviewIconSizeSmall remotePreviewLayoutParam.height = remotePreviewIconSizeSmall + + if(isCircle){ + controlPanel?.visibility = View.GONE + layoutMini.visibility = View.GONE + } else { + controlPanel?.visibility = View.VISIBLE + layoutMini.visibility = View.VISIBLE + } } mPublisherViewContainer.layoutParams = localPreviewLayoutParam @@ -753,13 +806,18 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session ).toInt() } - private class MyGestureListener(val onTabCall: () -> Unit) : GestureDetector.SimpleOnGestureListener() { + private class MyGestureListener(val onTabCall: () -> Unit, val miniCircleDoubleTap: () -> Unit) : GestureDetector.SimpleOnGestureListener() { override fun onSingleTapConfirmed(event: MotionEvent): Boolean { onTabCall() return true } + override fun onDoubleTap(e: MotionEvent?): Boolean { + miniCircleDoubleTap() + return super.onDoubleTap(e) + } + } companion object { diff --git a/android/app/src/main/res/drawable/circle_shape.xml b/android/app/src/main/res/drawable/circle_shape.xml index 2cb09469..27b49f9b 100644 --- a/android/app/src/main/res/drawable/circle_shape.xml +++ b/android/app/src/main/res/drawable/circle_shape.xml @@ -3,10 +3,10 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval"> - - + - + + + diff --git a/android/app/src/main/res/layout/activity_video_call.xml b/android/app/src/main/res/layout/activity_video_call.xml index 6dbdaa1f..efc97746 100644 --- a/android/app/src/main/res/layout/activity_video_call.xml +++ b/android/app/src/main/res/layout/activity_video_call.xml @@ -47,12 +47,32 @@ + + + + + diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml index 29782be0..f52c2cf2 100644 --- a/android/app/src/main/res/values/colors.xml +++ b/android/app/src/main/res/values/colors.xml @@ -5,6 +5,10 @@ #fc3850 #e4e9f2 + #80757575 + #00ffffff + + #827b92 #484258 diff --git a/lib/config/config.dart b/lib/config/config.dart index 8241b36c..eb361798 100644 --- a/lib/config/config.dart +++ b/lib/config/config.dart @@ -5,8 +5,8 @@ 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 = 'https://hmgwebservices.com/'; -// const BASE_URL = 'https://uat.hmgwebservices.com/'; +// const BASE_URL = 'https://hmgwebservices.com/'; +const BASE_URL = 'https://uat.hmgwebservices.com/'; const PHARMACY_ITEMS_URL = "Services/Lists.svc/REST/GetPharmcyItems_Region_enh"; const PHARMACY_LIST_URL = "Services/Patients.svc/REST/GetPharmcyList"; const PATIENT_PROGRESS_NOTE_URL = "Services/DoctorApplication.svc/REST/GetProgressNoteForInPatient"; diff --git a/lib/core/service/VideoCallService.dart b/lib/core/service/VideoCallService.dart index 0ae61a55..114d627f 100644 --- a/lib/core/service/VideoCallService.dart +++ b/lib/core/service/VideoCallService.dart @@ -27,9 +27,9 @@ class VideoCallService extends BaseService{ this.patient = patientModel; DoctorProfileModel doctorProfile = await getDoctorProfile(isGetProfile: true); await VideoChannel.openVideoCallScreen( - kToken: startCallRes.openTokenID, - kSessionId: startCallRes.openSessionID, - kApiKey: '46209962',//'46209962', + kToken:"T1==cGFydG5lcl9pZD00NzI0Nzk1NCZzaWc9NGIyZDljOTY3YjFiNWU1YzUzNzFmMjIyNjJmNmEzY2Y5NzZjOTdlYzpzZXNzaW9uX2lkPTFfTVg0ME56STBOemsxTkg1LU1UWXlNekEyTlRRMU9EVXhObjVrVFRoMFlVdFJXaXRYTWpadFZGZHFhSGxZVGpOdE1UVi1mZyZjcmVhdGVfdGltZT0xNjIzMDY1NDk1Jm5vbmNlPTAuMjM2Mjk0NTIwMTkyOTA4OTcmcm9sZT1wdWJsaXNoZXImZXhwaXJlX3RpbWU9MTYyNTY1NzQ5NCZpbml0aWFsX2xheW91dF9jbGFzc19saXN0PQ==", //startCallRes.openTokenID, + kSessionId: "1_MX40NzI0Nzk1NH5-MTYyMzA2NTQ1ODUxNn5kTTh0YUtRWitXMjZtVFdqaHlYTjNtMTV-fg",//startCallRes.openSessionID, + kApiKey:'47247954',// '46209962', vcId: patient.vcId, patientName: patient.fullName ?? (patient.firstName != null ? "${patient.firstName} ${patient.lastName}" : "-"), tokenID: await sharedPref.getString(TOKEN), From b8de9c9a14f98e41e90264e437c6533d6690bcf3 Mon Sep 17 00:00:00 2001 From: mosazaid Date: Thu, 17 Jun 2021 10:11:19 +0300 Subject: [PATCH 22/30] circle screen fix bug, and mini screen design --- .../main/kotlin/com/hmg/hmgDr/MainActivity.kt | 6 ++- .../hmgDr/ui/fragment/VideoCallFragment.kt | 28 +++++----- android/app/src/main/res/drawable/ic_mini.xml | 2 +- .../main/res/layout/activity_video_call.xml | 53 +++++++++---------- 4 files changed, 47 insertions(+), 42 deletions(-) diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt index af19306d..fcc3f2e3 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt @@ -151,7 +151,11 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, } else if (resultCode == Activity.RESULT_CANCELED) { val callResponse: HashMap = HashMap() callResponse["callResponse"] = "CallEnd" - result?.success(callResponse) + try { + result?.success(callResponse) + } catch (e : Exception){ + Log.e("onVideoCallFinished", "${e.message}.") + } } } diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt index 68db00fb..51ce1948 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt @@ -64,7 +64,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session private lateinit var mPublisherViewIcon: View private lateinit var mSubscriberViewContainer: FrameLayout private lateinit var mSubscriberViewIcon: ImageView - private var controlPanel: ConstraintLayout? = null + private lateinit var controlPanel: ConstraintLayout private var apiKey: String? = null private var sessionId: String? = null @@ -300,7 +300,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session private fun hiddenButtons() { mVolHandler = Handler() - mVolRunnable = Runnable { controlPanel!!.visibility = View.GONE } + mVolRunnable = Runnable { controlPanel.visibility = View.GONE } mVolHandler!!.postDelayed(mVolRunnable!!, (5 * 1000).toLong()) } @@ -496,27 +496,27 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session private fun onMiniCircleClicked(){ if (isCircle) { + videoCallContainer.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.text_color)) + mSubscriberViewContainer.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.remoteBackground)) dialog?.window?.setLayout( 400, 600 ) - videoCallContainer.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.text_color)) - mSubscriberViewContainer.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.remoteBackground)) } else { + videoCallContainer.background = ContextCompat.getDrawable(requireContext(), R.drawable.circle_shape) + mSubscriberViewContainer.background = ContextCompat.getDrawable(requireContext(), R.drawable.circle_shape) dialog?.window?.setLayout( 200, 200 ) - videoCallContainer.background = ContextCompat.getDrawable(requireContext(), R.drawable.circle_shape) - mSubscriberViewContainer.background = ContextCompat.getDrawable(requireContext(), R.drawable.circle_shape) } isCircle = !isCircle if(isCircle){ - controlPanel?.visibility = View.GONE + controlPanel.visibility = View.GONE layoutMini.visibility = View.GONE } else { - controlPanel?.visibility = View.VISIBLE + controlPanel.visibility = View.VISIBLE layoutMini.visibility = View.VISIBLE } } @@ -612,10 +612,10 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session remotePreviewLayoutParam.height = remotePreviewIconSizeSmall if(isCircle){ - controlPanel?.visibility = View.GONE + controlPanel.visibility = View.GONE layoutMini.visibility = View.GONE } else { - controlPanel?.visibility = View.VISIBLE + controlPanel.visibility = View.VISIBLE layoutMini.visibility = View.VISIBLE } } @@ -722,9 +722,11 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session } private fun showControlPanelTemporarily() { - controlPanel!!.visibility = View.VISIBLE - mVolHandler!!.removeCallbacks(mVolRunnable!!) - mVolHandler!!.postDelayed(mVolRunnable!!, (5 * 1000).toLong()) + if (!isCircle){ + controlPanel.visibility = View.VISIBLE + mVolHandler!!.removeCallbacks(mVolRunnable!!) + mVolHandler!!.postDelayed(mVolRunnable!!, (5 * 1000).toLong()) + } } /* Reset position of Floating Widget view on dragging */ diff --git a/android/app/src/main/res/drawable/ic_mini.xml b/android/app/src/main/res/drawable/ic_mini.xml index 300a9358..128a7430 100644 --- a/android/app/src/main/res/drawable/ic_mini.xml +++ b/android/app/src/main/res/drawable/ic_mini.xml @@ -1,5 +1,5 @@ - + diff --git a/android/app/src/main/res/layout/activity_video_call.xml b/android/app/src/main/res/layout/activity_video_call.xml index efc97746..a7470da3 100644 --- a/android/app/src/main/res/layout/activity_video_call.xml +++ b/android/app/src/main/res/layout/activity_video_call.xml @@ -37,9 +37,9 @@ android:id="@+id/cmTimer" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:padding="4dp" android:textColor="@color/white" android:textSize="16sp" - android:padding="4dp" android:textStyle="bold" tools:text="25:45" /> @@ -47,39 +47,38 @@ - - - - - + app:layout_constraintTop_toBottomOf="@+id/layout_name"> + + + + + android:src="@drawable/camera_back" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toEndOf="@id/btn_mic" + app:layout_constraintTop_toTopOf="parent" /> Date: Thu, 17 Jun 2021 10:14:00 +0300 Subject: [PATCH 23/30] fix drop video call issue --- .../kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt | 4 ++-- lib/config/config.dart | 4 ++-- .../model/patient_muse/PatientSearchRequestModel.dart | 3 ++- lib/core/service/VideoCallService.dart | 8 ++++---- lib/core/viewModel/scan_qr_view_model.dart | 2 +- .../profile/profile_screen/patient_profile_screen.dart | 4 ++-- 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt index 9a2deab6..66777d93 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt @@ -358,7 +358,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session override fun onError(session: Session, opentokError: OpentokError) { Log.d(TAG, "onError: Error (" + opentokError.message + ") in session " + session.sessionId) - videoCallResponseListener?.errorHandle("Error (" + opentokError.message + ") in session ") + // videoCallResponseListener?.errorHandle("Error (" + opentokError.message + ") in session ") // dialog?.dismiss() } @@ -398,7 +398,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session override fun onError(publisherKit: PublisherKit?, opentokError: OpentokError) { Log.d(VideoCallFragment.TAG, "onError: Error (" + opentokError.message + ") in publisher") - videoCallResponseListener?.errorHandle("Error (" + opentokError.message + ") in publisher") + // videoCallResponseListener?.errorHandle("Error (" + opentokError.message + ") in publisher") // dialog?.dismiss() } diff --git a/lib/config/config.dart b/lib/config/config.dart index 8241b36c..eb361798 100644 --- a/lib/config/config.dart +++ b/lib/config/config.dart @@ -5,8 +5,8 @@ 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 = 'https://hmgwebservices.com/'; -// const BASE_URL = 'https://uat.hmgwebservices.com/'; +// const BASE_URL = 'https://hmgwebservices.com/'; +const BASE_URL = 'https://uat.hmgwebservices.com/'; const PHARMACY_ITEMS_URL = "Services/Lists.svc/REST/GetPharmcyItems_Region_enh"; const PHARMACY_LIST_URL = "Services/Patients.svc/REST/GetPharmcyList"; const PATIENT_PROGRESS_NOTE_URL = "Services/DoctorApplication.svc/REST/GetProgressNoteForInPatient"; diff --git a/lib/core/model/patient_muse/PatientSearchRequestModel.dart b/lib/core/model/patient_muse/PatientSearchRequestModel.dart index 2918a393..c2658c9f 100644 --- a/lib/core/model/patient_muse/PatientSearchRequestModel.dart +++ b/lib/core/model/patient_muse/PatientSearchRequestModel.dart @@ -12,7 +12,7 @@ class PatientSearchRequestModel { String mobileNo; String identificationNo; int nursingStationID; - int clinicID; + int clinicID=0; PatientSearchRequestModel( {this.doctorID = 0, @@ -63,6 +63,7 @@ class PatientSearchRequestModel { data['IdentificationNo'] = this.identificationNo; data['NursingStationID'] = this.nursingStationID; data['ClinicID'] = this.clinicID; + data['ProjectID'] = 0; return data; } } diff --git a/lib/core/service/VideoCallService.dart b/lib/core/service/VideoCallService.dart index 0ae61a55..b3bc3c8d 100644 --- a/lib/core/service/VideoCallService.dart +++ b/lib/core/service/VideoCallService.dart @@ -27,9 +27,9 @@ class VideoCallService extends BaseService{ this.patient = patientModel; DoctorProfileModel doctorProfile = await getDoctorProfile(isGetProfile: true); await VideoChannel.openVideoCallScreen( - kToken: startCallRes.openTokenID, - kSessionId: startCallRes.openSessionID, - kApiKey: '46209962',//'46209962', + kToken: "T1==cGFydG5lcl9pZD00NzI0Nzk1NCZzaWc9NGIyZDljOTY3YjFiNWU1YzUzNzFmMjIyNjJmNmEzY2Y5NzZjOTdlYzpzZXNzaW9uX2lkPTFfTVg0ME56STBOemsxTkg1LU1UWXlNekEyTlRRMU9EVXhObjVrVFRoMFlVdFJXaXRYTWpadFZGZHFhSGxZVGpOdE1UVi1mZyZjcmVhdGVfdGltZT0xNjIzMDY1NDk1Jm5vbmNlPTAuMjM2Mjk0NTIwMTkyOTA4OTcmcm9sZT1wdWJsaXNoZXImZXhwaXJlX3RpbWU9MTYyNTY1NzQ5NCZpbml0aWFsX2xheW91dF9jbGFzc19saXN0PQ==",//tartCallRes.openTokenID, + kSessionId: '1_MX40NzI0Nzk1NH5-MTYyMzA2NTQ1ODUxNn5kTTh0YUtRWitXMjZtVFdqaHlYTjNtMTV-fg',//startCallRes.openSessionID, + kApiKey: '47247954',//'46209962', vcId: patient.vcId, patientName: patient.fullName ?? (patient.firstName != null ? "${patient.firstName} ${patient.lastName}" : "-"), tokenID: await sharedPref.getString(TOKEN), @@ -79,7 +79,7 @@ class VideoCallService extends BaseService{ endCallReq.generalid = 'Cs2020@2016\$2958'; endCallReq.vCID = vCID; endCallReq.isDestroy = isPatient; - await _liveCarePatientServices.endCall(endCallReq); + // await _liveCarePatientServices.endCall(endCallReq); if (_liveCarePatientServices.hasError) { error = _liveCarePatientServices.error; } diff --git a/lib/core/viewModel/scan_qr_view_model.dart b/lib/core/viewModel/scan_qr_view_model.dart index ff934f79..86e975e3 100644 --- a/lib/core/viewModel/scan_qr_view_model.dart +++ b/lib/core/viewModel/scan_qr_view_model.dart @@ -13,7 +13,7 @@ class ScanQrViewModel extends BaseViewModel { await getDoctorProfile(); setState(ViewState.Busy); - await _scanQrService.getInPatient(requestModel, false); + await _scanQrService.getInPatient(requestModel, true); if (_scanQrService.hasError) { error = _scanQrService.error; 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 9aa95d16..d0117b40 100644 --- a/lib/screens/patients/profile/profile_screen/patient_profile_screen.dart +++ b/lib/screens/patients/profile/profile_screen/patient_profile_screen.dart @@ -343,14 +343,14 @@ class _PatientProfileScreenState extends State var asd = ""; } else { GifLoaderDialogUtils.showMyDialog(context); - // await model.startCall( isReCall : false, vCID: patient.vcId); + // await model.startCall( isReCall : false, vCID: patient.vcId); if(model.state == ViewState.ErrorLocal) { GifLoaderDialogUtils.hideDialog(context); Helpers.showErrorToast(model.error); } else { await model.getDoctorProfile(); - // patient.appointmentNo = model.startCallRes.appointmentNo; + // patient.appointmentNo = model.startCallRes.appointmentNo; patient.episodeNo = 0; GifLoaderDialogUtils.hideDialog(context); From 3c2abb66c62d2ba59218a7ef77bcd9622059a66b Mon Sep 17 00:00:00 2001 From: mosazaid Date: Thu, 17 Jun 2021 10:29:52 +0300 Subject: [PATCH 24/30] add video call permissions --- .../com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt index 52bafdfb..833855a0 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt @@ -329,7 +329,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session @AfterPermissionGranted(RC_VIDEO_APP_PERM) private fun requestPermissions() { - val perms = arrayOf(Manifest.permission.INTERNET, Manifest.permission.CAMERA) + val perms = arrayOf(Manifest.permission.INTERNET, Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO, Manifest.permission.MODIFY_AUDIO_SETTINGS, Manifest.permission.CALL_PHONE) if (EasyPermissions.hasPermissions(requireContext(), *perms)) { try { mSession = Session.Builder(context, apiKey, sessionId).build() @@ -496,19 +496,19 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session private fun onMiniCircleClicked(){ if (isCircle) { - videoCallContainer.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.text_color)) - mSubscriberViewContainer.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.remoteBackground)) dialog?.window?.setLayout( 400, 600 ) + videoCallContainer.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.text_color)) + mSubscriberViewContainer.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.remoteBackground)) } else { - videoCallContainer.background = ContextCompat.getDrawable(requireContext(), R.drawable.circle_shape) - mSubscriberViewContainer.background = ContextCompat.getDrawable(requireContext(), R.drawable.circle_shape) dialog?.window?.setLayout( 200, 200 ) + videoCallContainer.background = ContextCompat.getDrawable(requireContext(), R.drawable.circle_shape) + mSubscriberViewContainer.background = ContextCompat.getDrawable(requireContext(), R.drawable.circle_shape) } isCircle = !isCircle From 654a86a9b64d7cf8c58d8232c9d17916e276fdc4 Mon Sep 17 00:00:00 2001 From: Mohammad Aljammal Date: Thu, 17 Jun 2021 12:17:56 +0300 Subject: [PATCH 25/30] Add App Permissions Utils --- lib/core/service/VideoCallService.dart | 1 + .../patient_profile_screen.dart | 6 +- lib/util/NotificationPermissionUtils.dart | 42 +++++++++ lib/widgets/dialog/AskPermissionDialog.dart | 90 +++++++++++++++++++ 4 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 lib/util/NotificationPermissionUtils.dart create mode 100644 lib/widgets/dialog/AskPermissionDialog.dart diff --git a/lib/core/service/VideoCallService.dart b/lib/core/service/VideoCallService.dart index b3bc3c8d..53739c9c 100644 --- a/lib/core/service/VideoCallService.dart +++ b/lib/core/service/VideoCallService.dart @@ -70,6 +70,7 @@ class VideoCallService extends BaseService{ }); }); + } Future endCall(int vCID, bool isPatient) async { hasError = false; 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 d0117b40..ea61359a 100644 --- a/lib/screens/patients/profile/profile_screen/patient_profile_screen.dart +++ b/lib/screens/patients/profile/profile_screen/patient_profile_screen.dart @@ -13,6 +13,7 @@ import 'package:doctor_app_flutter/screens/live_care/end_call_screen.dart'; import 'package:doctor_app_flutter/screens/patients/profile/profile_screen/profile_gird_for_InPatient.dart'; import 'package:doctor_app_flutter/screens/patients/profile/profile_screen/profile_gird_for_other.dart'; import 'package:doctor_app_flutter/screens/patients/profile/profile_screen/profile_gird_for_search.dart'; +import 'package:doctor_app_flutter/util/NotificationPermissionUtils.dart'; import 'package:doctor_app_flutter/util/VideoChannel.dart'; import 'package:doctor_app_flutter/util/dr_app_toast_msg.dart'; import 'package:doctor_app_flutter/util/helpers.dart'; @@ -354,7 +355,10 @@ class _PatientProfileScreenState extends State patient.episodeNo = 0; GifLoaderDialogUtils.hideDialog(context); - locator().openVideo(model.startCallRes, patient, callConnected, callDisconnected); + AppPermissionsUtils.requestVideoCallPermission(context: context,onTapGrant: (){ + locator().openVideo(model.startCallRes, patient, callConnected, callDisconnected); + }); + // await VideoChannel.openVideoCallScreen( // kToken: "T1==cGFydG5lcl9pZD00NzI0Nzk1NCZzaWc9NGIyZDljOTY3YjFiNWU1YzUzNzFmMjIyNjJmNmEzY2Y5NzZjOTdlYzpzZXNzaW9uX2lkPTFfTVg0ME56STBOemsxTkg1LU1UWXlNekEyTlRRMU9EVXhObjVrVFRoMFlVdFJXaXRYTWpadFZGZHFhSGxZVGpOdE1UVi1mZyZjcmVhdGVfdGltZT0xNjIzMDY1NDk1Jm5vbmNlPTAuMjM2Mjk0NTIwMTkyOTA4OTcmcm9sZT1wdWJsaXNoZXImZXhwaXJlX3RpbWU9MTYyNTY1NzQ5NCZpbml0aWFsX2xheW91dF9jbGFzc19saXN0PQ==",//model.startCallRes.openTokenID, diff --git a/lib/util/NotificationPermissionUtils.dart b/lib/util/NotificationPermissionUtils.dart new file mode 100644 index 00000000..8950fae3 --- /dev/null +++ b/lib/util/NotificationPermissionUtils.dart @@ -0,0 +1,42 @@ +import 'package:doctor_app_flutter/widgets/dialog/AskPermissionDialog.dart'; +import 'package:doctor_app_flutter/widgets/transitions/slide_up_page.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:permission_handler/permission_handler.dart'; + + +class AppPermissionsUtils { + + static requestVideoCallPermission({BuildContext context, String type,Function onTapGrant}) async { + + var cameraPermission = Permission.camera; + var microphonePermission = Permission.microphone; + PermissionStatus permissionCameraStatus = await cameraPermission.status; + PermissionStatus permissionMicrophoneStatus = await microphonePermission.status; + + if (permissionCameraStatus.isPermanentlyDenied || permissionMicrophoneStatus.isPermanentlyDenied) { + await _showPermissionDialog(context, type,onTapGrant); + } else if (!permissionCameraStatus.isGranted || !permissionMicrophoneStatus.isGranted) { + permissionCameraStatus = await cameraPermission.request(); + permissionMicrophoneStatus = await microphonePermission.request(); + if (permissionCameraStatus.isDenied || permissionMicrophoneStatus.isDenied) + await _showPermissionDialog(context, type,onTapGrant); + else + onTapGrant(); + } else if (permissionCameraStatus.isDenied || permissionMicrophoneStatus.isDenied) + await _showPermissionDialog(context, type,onTapGrant); + else + onTapGrant(); + } + + static _showPermissionDialog(BuildContext context, String type,Function onTapGrant) async { + Navigator.push( + context, SlideUpPageRoute(widget: AskPermissionDialog(type: type,onTapGrant: onTapGrant,))); + } + + static Future isVideoCallPermissionGranted() async { + PermissionStatus permissionCameraStatus = await Permission.camera.status; + PermissionStatus permissionMicrophoneStatus = await Permission.microphone.status; + return permissionCameraStatus.isGranted && permissionMicrophoneStatus.isGranted; + } +} diff --git a/lib/widgets/dialog/AskPermissionDialog.dart b/lib/widgets/dialog/AskPermissionDialog.dart new file mode 100644 index 00000000..58718373 --- /dev/null +++ b/lib/widgets/dialog/AskPermissionDialog.dart @@ -0,0 +1,90 @@ +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/buttons/secondary_button.dart'; +import 'package:eva_icons_flutter/eva_icons_flutter.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:permission_handler/permission_handler.dart'; + +class AskPermissionDialog extends StatefulWidget { + final String type; + final Function onTapGrant; + + AskPermissionDialog({this.type, this.onTapGrant}); + + @override + _AskPermissionDialogState createState() => _AskPermissionDialogState(); +} + +class _AskPermissionDialogState extends State { + getText() { + return "Turn on your Camera, Microphone to start video call"; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + automaticallyImplyLeading: false, + elevation: 0.5, + actions: [ + Padding( + padding: EdgeInsets.only(right: 18.0), + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: Feedback.wrapForTap(() { + Navigator.pop(context); + }, context), + child: + Icon(EvaIcons.close, color: Theme.of(context).primaryColor), + ), + ) + ], + ), + body: Container( + margin: EdgeInsets.symmetric(horizontal: 48), + child: Center( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + AppText( + "🙋‍♀", + style: "headline1", + color: Colors.black, + ), + AppText( + "Don't miss out!", + style: "headline4", + color: Colors.black, + ), + SizedBox( + height: 8, + ), + AppText( + getText(), + color: Colors.grey, + style: "bodyText2", + textAlign: TextAlign.center, + ), + SizedBox( + height: MediaQuery.of(context).size.height / 6, + ), + AppButton( + fontColor: Theme.of(context).backgroundColor, + color: Colors.red[700], + title: "Turn On Camera, Microphone", + onPressed: () async { + openAppSettings().then((value) { + Navigator.pop(context); + widget.onTapGrant(); + }); + }, + ), + ], + ), + ), + ), + ); + } +} From c67f3d10ad1b43b2d9e7c7e289e5384f297da1c8 Mon Sep 17 00:00:00 2001 From: mosazaid Date: Thu, 17 Jun 2021 13:54:50 +0300 Subject: [PATCH 26/30] fix circle video stream --- .../hmgDr/ui/fragment/VideoCallFragment.kt | 96 +++-- .../hmg/hmgDr/util/DynamicVideoRenderer.kt | 379 ++++++++++++++++++ .../util/ThumbnailCircleVideoRenderer.kt | 357 +++++++++++++++++ .../main/res/layout/activity_video_call.xml | 13 +- 4 files changed, 817 insertions(+), 28 deletions(-) create mode 100644 android/app/src/main/kotlin/com/hmg/hmgDr/util/DynamicVideoRenderer.kt create mode 100644 android/app/src/main/kotlin/com/hmg/hmgDr/util/ThumbnailCircleVideoRenderer.kt diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt index 833855a0..5b98b27d 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt @@ -27,6 +27,8 @@ import com.hmg.hmgDr.ui.VideoCallContract.VideoCallPresenter import com.hmg.hmgDr.ui.VideoCallContract.VideoCallView import com.hmg.hmgDr.ui.VideoCallPresenterImpl import com.hmg.hmgDr.ui.VideoCallResponseListener +import com.hmg.hmgDr.util.DynamicVideoRenderer +import com.hmg.hmgDr.util.ThumbnailCircleVideoRenderer import com.opentok.android.* import com.opentok.android.PublisherKit.PublisherListener import pub.devrel.easypermissions.AfterPermissionGranted @@ -60,6 +62,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session private var mVolRunnable: Runnable? = null private var mConnectedRunnable: Runnable? = null + private lateinit var thumbnail_container: FrameLayout private lateinit var mPublisherViewContainer: FrameLayout private lateinit var mPublisherViewIcon: View private lateinit var mSubscriberViewContainer: FrameLayout @@ -220,6 +223,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session layoutName = view.findViewById(R.id.layout_name) layoutMini = view.findViewById(R.id.layout_mini) icMini = view.findViewById(R.id.ic_mini) + thumbnail_container = view.findViewById(R.id.thumbnail_container) mPublisherViewContainer = view.findViewById(R.id.local_video_view_container) mPublisherViewIcon = view.findViewById(R.id.local_video_view_icon) mSubscriberViewIcon = view.findViewById(R.id.remote_video_view_icon) @@ -345,12 +349,16 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session override fun onConnected(session: Session?) { Log.i(TAG, "Session Connected") - mPublisher = Publisher.Builder(requireContext()).build() + mPublisher = Publisher.Builder(requireContext()) +// .name("publisher") +// .renderer(ThumbnailCircleVideoRenderer(requireContext())) + .build() mPublisher!!.setPublisherListener(this) - mPublisherViewContainer.addView(mPublisher!!.view) - if (mPublisher!!.getView() is GLSurfaceView) { - (mPublisher!!.getView() as GLSurfaceView).setZOrderOnTop(true) + if (mPublisher!!.view is GLSurfaceView) { + (mPublisher!!.view as GLSurfaceView).setZOrderOnTop(true) } + + mPublisherViewContainer.addView(mPublisher!!.view) mSession!!.publish(mPublisher) if (!resume) { @@ -368,7 +376,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session override fun onError(session: Session, opentokError: OpentokError) { Log.d(TAG, "onError: Error (" + opentokError.message + ") in session " + session.sessionId) - // videoCallResponseListener?.errorHandle("Error (" + opentokError.message + ") in session ") + // videoCallResponseListener?.errorHandle("Error (" + opentokError.message + ") in session ") // dialog?.dismiss() } @@ -391,7 +399,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session return } if (mSubscriber!!.stream == stream) { - mSubscriberViewContainer!!.removeView(mSubscriber!!.view) + mSubscriberViewContainer.removeView(mSubscriber!!.view) mSubscriber!!.destroy() mSubscriber = null } @@ -408,13 +416,49 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session override fun onError(publisherKit: PublisherKit?, opentokError: OpentokError) { Log.d(VideoCallFragment.TAG, "onError: Error (" + opentokError.message + ") in publisher") - // videoCallResponseListener?.errorHandle("Error (" + opentokError.message + ") in publisher") + // videoCallResponseListener?.errorHandle("Error (" + opentokError.message + ") in publisher") // dialog?.dismiss() } override fun onVideoDataReceived(subscriberKit: SubscriberKit?) { mSubscriber!!.setStyle(BaseVideoRenderer.STYLE_VIDEO_SCALE, BaseVideoRenderer.STYLE_VIDEO_FILL) - mSubscriberViewContainer!!.addView(mSubscriber!!.view) + (mSubscriber!!.renderer as DynamicVideoRenderer).enableThumbnailCircle(false) + mSubscriberViewContainer.addView(mSubscriber!!.view) +// switchToThumbnailCircle() + } + + fun switchToThumbnailCircle() { + thumbnail_container.postDelayed({ + val view = mSubscriber!!.view + if (view.parent != null) { + (view.parent as ViewGroup).removeView(view) + } + if (view is GLSurfaceView) { + view.setZOrderOnTop(true) + if (mSubscriber!!.renderer is DynamicVideoRenderer) { + (mSubscriber!!.renderer as DynamicVideoRenderer).enableThumbnailCircle(true) + thumbnail_container.addView(view) + } + } + switchToFullScreenView() + }, 4000) + } + + fun switchToFullScreenView() { + mSubscriberViewContainer.postDelayed({ + val view = mSubscriber!!.view + if (view.parent != null) { + (view.parent as ViewGroup).removeView(view) + } + if (view is GLSurfaceView) { + view.setZOrderOnTop(false) + if (mSubscriber!!.renderer is DynamicVideoRenderer) { + (mSubscriber!!.renderer as DynamicVideoRenderer).enableThumbnailCircle(false) + mSubscriberViewContainer.addView(view) + } + } + switchToThumbnailCircle() + }, 4000) } override fun onVideoDisabled(subscriberKit: SubscriberKit?, s: String?) {} @@ -426,7 +470,9 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session override fun onVideoDisableWarningLifted(subscriberKit: SubscriberKit?) {} private fun subscribeToStream(stream: Stream) { - mSubscriber = Subscriber.Builder(requireContext(), stream).build() + mSubscriber = Subscriber.Builder(requireContext(), stream) + .renderer(DynamicVideoRenderer(requireContext())) + .build() mSubscriber!!.setVideoListener(this) mSession!!.subscribe(mSubscriber) } @@ -488,31 +534,29 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session disconnectSession() } - private fun miniCircleDoubleTap(){ - if (isCircle){ + private fun miniCircleDoubleTap() { + if (isCircle) { onMiniCircleClicked() } } - private fun onMiniCircleClicked(){ + private fun onMiniCircleClicked() { if (isCircle) { dialog?.window?.setLayout( 400, 600 ) - videoCallContainer.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.text_color)) - mSubscriberViewContainer.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.remoteBackground)) + (mSubscriber!!.renderer as DynamicVideoRenderer).enableThumbnailCircle(false) } else { dialog?.window?.setLayout( - 200, - 200 + 300, + 300 ) - videoCallContainer.background = ContextCompat.getDrawable(requireContext(), R.drawable.circle_shape) - mSubscriberViewContainer.background = ContextCompat.getDrawable(requireContext(), R.drawable.circle_shape) + (mSubscriber!!.renderer as DynamicVideoRenderer).enableThumbnailCircle(true) } isCircle = !isCircle - if(isCircle){ + if (isCircle) { controlPanel.visibility = View.GONE layoutMini.visibility = View.GONE } else { @@ -548,16 +592,16 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session val btnMinimizeLayoutParam: ConstraintLayout.LayoutParams = btnMinimize.layoutParams as ConstraintLayout.LayoutParams val mCallBtnLayoutParam: ConstraintLayout.LayoutParams = mCallBtn.layoutParams as ConstraintLayout.LayoutParams - val localPreviewMargin : Int = context!!.resources.getDimension(R.dimen.local_preview_margin_top).toInt() - val localPreviewWidth : Int = context!!.resources.getDimension(R.dimen.local_preview_width).toInt() - val localPreviewHeight : Int = context!!.resources.getDimension(R.dimen.local_preview_height).toInt() + val localPreviewMargin: Int = context!!.resources.getDimension(R.dimen.local_preview_margin_top).toInt() + val localPreviewWidth: Int = context!!.resources.getDimension(R.dimen.local_preview_width).toInt() + val localPreviewHeight: Int = context!!.resources.getDimension(R.dimen.local_preview_height).toInt() // val localPreviewIconSize: Int = context!!.resources.getDimension(R.dimen.local_back_icon_size).toInt() // val localPreviewMarginSmall : Int = context!!.resources.getDimension(R.dimen.local_preview_margin_small).toInt() // val localPreviewWidthSmall : Int = context!!.resources.getDimension(R.dimen.local_preview_width_small).toInt() // val localPreviewHeightSmall : Int = context!!.resources.getDimension(R.dimen.local_preview_height_small).toInt() // val localPreviewIconSmall: Int = context!!.resources.getDimension(R.dimen.local_back_icon_size_small).toInt() // val localPreviewLayoutIconParam : FrameLayout.LayoutParams - val localPreviewLayoutParam : RelativeLayout.LayoutParams = mPublisherViewContainer.layoutParams as RelativeLayout.LayoutParams + val localPreviewLayoutParam: RelativeLayout.LayoutParams = mPublisherViewContainer.layoutParams as RelativeLayout.LayoutParams val remotePreviewIconSize: Int = context!!.resources.getDimension(R.dimen.remote_back_icon_size).toInt() val remotePreviewIconSizeSmall: Int = context!!.resources.getDimension(R.dimen.remote_back_icon_size_small).toInt() @@ -611,7 +655,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session remotePreviewLayoutParam.width = remotePreviewIconSizeSmall remotePreviewLayoutParam.height = remotePreviewIconSizeSmall - if(isCircle){ + if (isCircle) { controlPanel.visibility = View.GONE layoutMini.visibility = View.GONE } else { @@ -651,7 +695,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session isSpeckerClicked = !isSpeckerClicked mSubscriber!!.subscribeToAudio = !isSpeckerClicked val res = if (isSpeckerClicked) R.drawable.audio_disabled else R.drawable.audio_enabled - mspeckerBtn!!.setImageResource(res) + mspeckerBtn.setImageResource(res) } } @@ -722,7 +766,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session } private fun showControlPanelTemporarily() { - if (!isCircle){ + if (!isCircle) { controlPanel.visibility = View.VISIBLE mVolHandler!!.removeCallbacks(mVolRunnable!!) mVolHandler!!.postDelayed(mVolRunnable!!, (5 * 1000).toLong()) diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/util/DynamicVideoRenderer.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/util/DynamicVideoRenderer.kt new file mode 100644 index 00000000..1a307eb5 --- /dev/null +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/util/DynamicVideoRenderer.kt @@ -0,0 +1,379 @@ +package com.hmg.hmgDr.util + +import android.content.Context +import android.content.res.Resources +import android.graphics.PixelFormat +import android.opengl.GLES20 +import android.opengl.GLSurfaceView +import android.opengl.Matrix +import android.view.View +import com.opentok.android.BaseVideoRenderer +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.nio.FloatBuffer +import java.nio.ShortBuffer +import java.util.concurrent.locks.ReentrantLock +import javax.microedition.khronos.egl.EGLConfig +import javax.microedition.khronos.opengles.GL10 + +/* +* https://nhancv.medium.com/android-how-to-make-a-circular-view-as-a-thumbnail-of-opentok-27992aee15c9 +* to solve make circle video stream +* */ + +class DynamicVideoRenderer(private val mContext: Context) : BaseVideoRenderer() { + private val mView: GLSurfaceView = GLSurfaceView(mContext) + private val mRenderer: MyRenderer + + interface DynamicVideoRendererMetadataListener { + fun onMetadataReady(metadata: ByteArray?) + } + + fun setDynamicVideoRendererMetadataListener(metadataListener: DynamicVideoRendererMetadataListener?) { + mRenderer.metadataListener = metadataListener + } + + fun enableThumbnailCircle(enable: Boolean) { + mRenderer.requestEnableThumbnailCircle = enable + } + + internal class MyRenderer : GLSurfaceView.Renderer { + var mTextureIds = IntArray(3) + var mScaleMatrix = FloatArray(16) + private val mVertexBuffer: FloatBuffer + private val mTextureBuffer: FloatBuffer + private val mDrawListBuffer: ShortBuffer + var requestEnableThumbnailCircle = false + var mVideoFitEnabled = true + var mVideoDisabled = false + private val mVertexIndex = shortArrayOf(0, 1, 2, 0, 2, 3) // order to draw + + // vertices + private val vertexShaderCode = """uniform mat4 uMVPMatrix;attribute vec4 aPosition; +attribute vec2 aTextureCoord; +varying vec2 vTextureCoord; +void main() { + gl_Position = uMVPMatrix * aPosition; + vTextureCoord = aTextureCoord; +} +""" + private val fragmentShaderCode = """precision mediump float; +uniform sampler2D Ytex; +uniform sampler2D Utex,Vtex; +uniform int enableCircle; +uniform vec2 radiusDp; +varying vec2 vTextureCoord; +void main(void) { + float nx,ny,r,g,b,y,u,v; + mediump vec4 txl,ux,vx; nx=vTextureCoord[0]; + ny=vTextureCoord[1]; + y=texture2D(Ytex,vec2(nx,ny)).r; + u=texture2D(Utex,vec2(nx,ny)).r; + v=texture2D(Vtex,vec2(nx,ny)).r; + y=1.1643*(y-0.0625); + u=u-0.5; + v=v-0.5; + r=y+1.5958*v; + g=y-0.39173*u-0.81290*v; + b=y+2.017*u; + if (enableCircle > 0) { + float radius = 0.5; + vec4 color0 = vec4(0.0, 0.0, 0.0, 0.0); + vec4 color1 = vec4(r, g, b, 1.0); + vec2 st = (gl_FragCoord.xy/radiusDp.xy); float dist = radius - distance(st,vec2(0.5)); + float t = 1.0; + if (dist < 0.0) t = 0.0; + gl_FragColor = mix(color0, color1, t); + } + else { + gl_FragColor = vec4(r, g, b, 1.0); + } +} +""" + var mFrameLock = ReentrantLock() + var mCurrentFrame: Frame? = null + private var mProgram = 0 + private var mTextureWidth = 0 + private var mTextureHeight = 0 + private var mViewportWidth = 0 + private var mViewportHeight = 0 + override fun onSurfaceCreated(gl: GL10, config: EGLConfig) { + gl.glClearColor(0f, 0f, 0f, 1f) + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT) + val vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, + vertexShaderCode) + val fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, + fragmentShaderCode) + mProgram = GLES20.glCreateProgram() // create empty OpenGL ES + // Program + GLES20.glAttachShader(mProgram, vertexShader) // add the vertex + // shader to program + GLES20.glAttachShader(mProgram, fragmentShader) // add the fragment + // shader to + // program + GLES20.glLinkProgram(mProgram) + val positionHandle = GLES20.glGetAttribLocation(mProgram, + "aPosition") + val textureHandle = GLES20.glGetAttribLocation(mProgram, + "aTextureCoord") + GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, + GLES20.GL_FLOAT, false, COORDS_PER_VERTEX * 4, + mVertexBuffer) + GLES20.glEnableVertexAttribArray(positionHandle) + GLES20.glVertexAttribPointer(textureHandle, + TEXTURECOORDS_PER_VERTEX, GLES20.GL_FLOAT, false, + TEXTURECOORDS_PER_VERTEX * 4, mTextureBuffer) + GLES20.glEnableVertexAttribArray(textureHandle) + GLES20.glUseProgram(mProgram) + var i = GLES20.glGetUniformLocation(mProgram, "Ytex") + GLES20.glUniform1i(i, 0) /* Bind Ytex to texture unit 0 */ + i = GLES20.glGetUniformLocation(mProgram, "Utex") + GLES20.glUniform1i(i, 1) /* Bind Utex to texture unit 1 */ + i = GLES20.glGetUniformLocation(mProgram, "Vtex") + GLES20.glUniform1i(i, 2) /* Bind Vtex to texture unit 2 */ + val radiusDpLocation = GLES20.glGetUniformLocation(mProgram, "radiusDp") + val radiusDp = (Resources.getSystem().displayMetrics.density * THUMBNAIL_SIZE).toInt() + GLES20.glUniform2f(radiusDpLocation, radiusDp.toFloat(), radiusDp.toFloat()) + mTextureWidth = 0 + mTextureHeight = 0 + } + + fun enableThumbnailCircle(enable: Boolean) { + GLES20.glUseProgram(mProgram) + val enableCircleLocation = GLES20.glGetUniformLocation(mProgram, "enableCircle") + GLES20.glUniform1i(enableCircleLocation, if (enable) 1 else 0) + } + + fun setupTextures(frame: Frame) { + if (mTextureIds[0] != 0) { + GLES20.glDeleteTextures(3, mTextureIds, 0) + } + GLES20.glGenTextures(3, mTextureIds, 0) + val w = frame.width + val h = frame.height + val hw = w + 1 shr 1 + val hh = h + 1 shr 1 + initializeTexture(GLES20.GL_TEXTURE0, mTextureIds[0], w, h) + initializeTexture(GLES20.GL_TEXTURE1, mTextureIds[1], hw, hh) + initializeTexture(GLES20.GL_TEXTURE2, mTextureIds[2], hw, hh) + mTextureWidth = frame.width + mTextureHeight = frame.height + } + + fun updateTextures(frame: Frame) { + val width = frame.width + val height = frame.height + val half_width = width + 1 shr 1 + val half_height = height + 1 shr 1 + val y_size = width * height + val uv_size = half_width * half_height + val bb = frame.buffer + // If we are reusing this frame, make sure we reset position and + // limit + bb.clear() + if (bb.remaining() == y_size + uv_size * 2) { + bb.position(0) + GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1) + GLES20.glPixelStorei(GLES20.GL_PACK_ALIGNMENT, 1) + GLES20.glActiveTexture(GLES20.GL_TEXTURE0) + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[0]) + GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, width, + height, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, + bb) + bb.position(y_size) + GLES20.glActiveTexture(GLES20.GL_TEXTURE1) + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[1]) + GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, + half_width, half_height, GLES20.GL_LUMINANCE, + GLES20.GL_UNSIGNED_BYTE, bb) + bb.position(y_size + uv_size) + GLES20.glActiveTexture(GLES20.GL_TEXTURE2) + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[2]) + GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, + half_width, half_height, GLES20.GL_LUMINANCE, + GLES20.GL_UNSIGNED_BYTE, bb) + } else { + mTextureWidth = 0 + mTextureHeight = 0 + } + } + + override fun onSurfaceChanged(gl: GL10, width: Int, height: Int) { + GLES20.glViewport(0, 0, width, height) + mViewportWidth = width + mViewportHeight = height + } + + var metadataListener: DynamicVideoRendererMetadataListener? = null + override fun onDrawFrame(gl: GL10) { + gl.glClearColor(0f, 0f, 0f, 0f) + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT) + mFrameLock.lock() + if (mCurrentFrame != null && !mVideoDisabled) { + GLES20.glUseProgram(mProgram) + if (mTextureWidth != mCurrentFrame!!.width + || mTextureHeight != mCurrentFrame!!.height) { + setupTextures(mCurrentFrame!!) + } + updateTextures(mCurrentFrame!!) + Matrix.setIdentityM(mScaleMatrix, 0) + var scaleX = 1.0f + var scaleY = 1.0f + val ratio = (mCurrentFrame!!.width.toFloat() + / mCurrentFrame!!.height) + val vratio = mViewportWidth.toFloat() / mViewportHeight + if (mVideoFitEnabled) { + if (ratio > vratio) { + scaleY = vratio / ratio + } else { + scaleX = ratio / vratio + } + } else { + if (ratio < vratio) { + scaleY = vratio / ratio + } else { + scaleX = ratio / vratio + } + } + Matrix.scaleM(mScaleMatrix, 0, + scaleX * if (mCurrentFrame!!.isMirroredX) -1.0f else 1.0f, + scaleY, 1f) + metadataListener?.onMetadataReady(mCurrentFrame!!.metadata) + val mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, + "uMVPMatrix") + GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, + mScaleMatrix, 0) + enableThumbnailCircle(requestEnableThumbnailCircle) + GLES20.glDrawElements(GLES20.GL_TRIANGLES, mVertexIndex.size, + GLES20.GL_UNSIGNED_SHORT, mDrawListBuffer) + } else { + //black frame when video is disabled + gl.glClearColor(0f, 0f, 0f, 1f) + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT) + } + mFrameLock.unlock() + } + + fun displayFrame(frame: Frame?) { + mFrameLock.lock() + if (mCurrentFrame != null) { + mCurrentFrame!!.recycle() + } + mCurrentFrame = frame + mFrameLock.unlock() + } + + fun disableVideo(b: Boolean) { + mFrameLock.lock() + mVideoDisabled = b + if (mVideoDisabled) { + if (mCurrentFrame != null) { + mCurrentFrame!!.recycle() + } + mCurrentFrame = null + } + mFrameLock.unlock() + } + + fun enableVideoFit(enableVideoFit: Boolean) { + mVideoFitEnabled = enableVideoFit + } + + companion object { + // number of coordinates per vertex in this array + const val COORDS_PER_VERTEX = 3 + const val TEXTURECOORDS_PER_VERTEX = 2 + var mXYZCoords = floatArrayOf( + -1.0f, 1.0f, 0.0f, // top left + -1.0f, -1.0f, 0.0f, // bottom left + 1.0f, -1.0f, 0.0f, // bottom right + 1.0f, 1.0f, 0.0f // top right + ) + var mUVCoords = floatArrayOf(0f, 0f, 0f, 1f, 1f, 1f, 1f, 0f) + fun initializeTexture(name: Int, id: Int, width: Int, height: Int) { + GLES20.glActiveTexture(name) + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, id) + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST.toFloat()) + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR.toFloat()) + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE.toFloat()) + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE.toFloat()) + GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, + width, height, 0, GLES20.GL_LUMINANCE, + GLES20.GL_UNSIGNED_BYTE, null) + } + + fun loadShader(type: Int, shaderCode: String?): Int { + val shader = GLES20.glCreateShader(type) + GLES20.glShaderSource(shader, shaderCode) + GLES20.glCompileShader(shader) + return shader + } + } + + init { + val bb = ByteBuffer.allocateDirect(mXYZCoords.size * 4) + bb.order(ByteOrder.nativeOrder()) + mVertexBuffer = bb.asFloatBuffer() + mVertexBuffer.put(mXYZCoords) + mVertexBuffer.position(0) + val tb = ByteBuffer.allocateDirect(mUVCoords.size * 4) + tb.order(ByteOrder.nativeOrder()) + mTextureBuffer = tb.asFloatBuffer() + mTextureBuffer.put(mUVCoords) + mTextureBuffer.position(0) + val dlb = ByteBuffer.allocateDirect(mVertexIndex.size * 2) + dlb.order(ByteOrder.nativeOrder()) + mDrawListBuffer = dlb.asShortBuffer() + mDrawListBuffer.put(mVertexIndex) + mDrawListBuffer.position(0) + } + } + + override fun onFrame(frame: Frame) { + mRenderer.displayFrame(frame) + mView.requestRender() + } + + override fun setStyle(key: String, value: String) { + if (STYLE_VIDEO_SCALE == key) { + if (STYLE_VIDEO_FIT == value) { + mRenderer.enableVideoFit(true) + } else if (STYLE_VIDEO_FILL == value) { + mRenderer.enableVideoFit(false) + } + } + } + + override fun onVideoPropertiesChanged(videoEnabled: Boolean) { + mRenderer.disableVideo(!videoEnabled) + } + + override fun getView(): View { + return mView + } + + override fun onPause() { + mView.onPause() + } + + override fun onResume() { + mView.onResume() + } + + companion object { + private const val THUMBNAIL_SIZE = 90 //in dp + } + + init { + mView.setEGLContextClientVersion(2) + mView.setEGLConfigChooser(8, 8, 8, 8, 16, 0) + mView.holder.setFormat(PixelFormat.TRANSLUCENT) + mRenderer = MyRenderer() + mView.setRenderer(mRenderer) + mView.renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/util/ThumbnailCircleVideoRenderer.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/util/ThumbnailCircleVideoRenderer.kt new file mode 100644 index 00000000..b9b5a245 --- /dev/null +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/util/ThumbnailCircleVideoRenderer.kt @@ -0,0 +1,357 @@ +package com.hmg.hmgDr.util + +import android.content.Context +import android.content.res.Resources +import android.graphics.PixelFormat +import android.opengl.GLES20 +import android.opengl.GLSurfaceView +import android.opengl.Matrix +import android.view.View +import com.opentok.android.BaseVideoRenderer +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.nio.FloatBuffer +import java.nio.ShortBuffer +import java.util.concurrent.locks.ReentrantLock +import javax.microedition.khronos.egl.EGLConfig +import javax.microedition.khronos.opengles.GL10 + + +class ThumbnailCircleVideoRenderer(private val mContext: Context) : BaseVideoRenderer() { + private val mView: GLSurfaceView = GLSurfaceView(mContext) + private val mRenderer: MyRenderer + + interface ThumbnailCircleVideoRendererMetadataListener { + fun onMetadataReady(metadata: ByteArray?) + } + + fun setThumbnailCircleVideoRendererMetadataListener(metadataListener: ThumbnailCircleVideoRendererMetadataListener?) { + mRenderer.metadataListener = metadataListener + } + + internal class MyRenderer : GLSurfaceView.Renderer { + var mTextureIds = IntArray(3) + var mScaleMatrix = FloatArray(16) + private val mVertexBuffer: FloatBuffer + private val mTextureBuffer: FloatBuffer + private val mDrawListBuffer: ShortBuffer + var mVideoFitEnabled = true + var mVideoDisabled = false + private val mVertexIndex = shortArrayOf(0, 1, 2, 0, 2, 3) // order to draw + + // vertices + private val vertexShaderCode = """uniform mat4 uMVPMatrix;attribute vec4 aPosition; +attribute vec2 aTextureCoord; +varying vec2 vTextureCoord; +void main() { + gl_Position = uMVPMatrix * aPosition; + vTextureCoord = aTextureCoord; +} +""" + private val fragmentShaderCode = """precision mediump float; +uniform sampler2D Ytex; +uniform sampler2D Utex,Vtex; +uniform vec2 radiusDp; +varying vec2 vTextureCoord; +void main(void) { + float nx,ny,r,g,b,y,u,v; + mediump vec4 txl,ux,vx; nx=vTextureCoord[0]; + ny=vTextureCoord[1]; + y=texture2D(Ytex,vec2(nx,ny)).r; + u=texture2D(Utex,vec2(nx,ny)).r; + v=texture2D(Vtex,vec2(nx,ny)).r; + y=1.1643*(y-0.0625); + u=u-0.5; + v=v-0.5; + r=y+1.5958*v; + g=y-0.39173*u-0.81290*v; + b=y+2.017*u; + float radius = 0.5; + vec4 color0 = vec4(0.0, 0.0, 0.0, 0.0); + vec4 color1 = vec4(r, g, b, 1.0); + vec2 st = (gl_FragCoord.xy/radiusDp.xy); float dist = radius - distance(st,vec2(0.5)); + float t = 1.0; + if (dist < 0.0) t = 0.0; + gl_FragColor = mix(color0, color1, t); +} +""" + var mFrameLock = ReentrantLock() + var mCurrentFrame: Frame? = null + private var mProgram = 0 + private var mTextureWidth = 0 + private var mTextureHeight = 0 + private var mViewportWidth = 0 + private var mViewportHeight = 0 + override fun onSurfaceCreated(gl: GL10, config: EGLConfig) { + gl.glClearColor(0f, 0f, 0f, 1f) + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT) + val vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, + vertexShaderCode) + val fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, + fragmentShaderCode) + mProgram = GLES20.glCreateProgram() // create empty OpenGL ES + // Program + GLES20.glAttachShader(mProgram, vertexShader) // add the vertex + // shader to program + GLES20.glAttachShader(mProgram, fragmentShader) // add the fragment + // shader to + // program + GLES20.glLinkProgram(mProgram) + val positionHandle = GLES20.glGetAttribLocation(mProgram, + "aPosition") + val textureHandle = GLES20.glGetAttribLocation(mProgram, + "aTextureCoord") + GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, + GLES20.GL_FLOAT, false, COORDS_PER_VERTEX * 4, + mVertexBuffer) + GLES20.glEnableVertexAttribArray(positionHandle) + GLES20.glVertexAttribPointer(textureHandle, + TEXTURECOORDS_PER_VERTEX, GLES20.GL_FLOAT, false, + TEXTURECOORDS_PER_VERTEX * 4, mTextureBuffer) + GLES20.glEnableVertexAttribArray(textureHandle) + GLES20.glUseProgram(mProgram) + var i = GLES20.glGetUniformLocation(mProgram, "Ytex") + GLES20.glUniform1i(i, 0) /* Bind Ytex to texture unit 0 */ + i = GLES20.glGetUniformLocation(mProgram, "Utex") + GLES20.glUniform1i(i, 1) /* Bind Utex to texture unit 1 */ + i = GLES20.glGetUniformLocation(mProgram, "Vtex") + GLES20.glUniform1i(i, 2) /* Bind Vtex to texture unit 2 */ + val radiusDpLocation = GLES20.glGetUniformLocation(mProgram, "radiusDp") + val radiusDp = (Resources.getSystem().displayMetrics.density * THUMBNAIL_SIZE).toInt() + GLES20.glUniform2f(radiusDpLocation, radiusDp.toFloat(), radiusDp.toFloat()) + mTextureWidth = 0 + mTextureHeight = 0 + } + + fun setupTextures(frame: Frame) { + if (mTextureIds[0] != 0) { + GLES20.glDeleteTextures(3, mTextureIds, 0) + } + GLES20.glGenTextures(3, mTextureIds, 0) + val w = frame.width + val h = frame.height + val hw = w + 1 shr 1 + val hh = h + 1 shr 1 + initializeTexture(GLES20.GL_TEXTURE0, mTextureIds[0], w, h) + initializeTexture(GLES20.GL_TEXTURE1, mTextureIds[1], hw, hh) + initializeTexture(GLES20.GL_TEXTURE2, mTextureIds[2], hw, hh) + mTextureWidth = frame.width + mTextureHeight = frame.height + } + + fun updateTextures(frame: Frame) { + val width = frame.width + val height = frame.height + val half_width = width + 1 shr 1 + val half_height = height + 1 shr 1 + val y_size = width * height + val uv_size = half_width * half_height + val bb = frame.buffer + // If we are reusing this frame, make sure we reset position and + // limit + bb.clear() + if (bb.remaining() == y_size + uv_size * 2) { + bb.position(0) + GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1) + GLES20.glPixelStorei(GLES20.GL_PACK_ALIGNMENT, 1) + GLES20.glActiveTexture(GLES20.GL_TEXTURE0) + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[0]) + GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, width, + height, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, + bb) + bb.position(y_size) + GLES20.glActiveTexture(GLES20.GL_TEXTURE1) + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[1]) + GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, + half_width, half_height, GLES20.GL_LUMINANCE, + GLES20.GL_UNSIGNED_BYTE, bb) + bb.position(y_size + uv_size) + GLES20.glActiveTexture(GLES20.GL_TEXTURE2) + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[2]) + GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, + half_width, half_height, GLES20.GL_LUMINANCE, + GLES20.GL_UNSIGNED_BYTE, bb) + } else { + mTextureWidth = 0 + mTextureHeight = 0 + } + } + + override fun onSurfaceChanged(gl: GL10, width: Int, height: Int) { + GLES20.glViewport(0, 0, width, height) + mViewportWidth = width + mViewportHeight = height + } + + var metadataListener: ThumbnailCircleVideoRendererMetadataListener? = null + override fun onDrawFrame(gl: GL10) { + gl.glClearColor(0f, 0f, 0f, 0f) + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT) + mFrameLock.lock() + if (mCurrentFrame != null && !mVideoDisabled) { + GLES20.glUseProgram(mProgram) + if (mTextureWidth != mCurrentFrame!!.width + || mTextureHeight != mCurrentFrame!!.height) { + setupTextures(mCurrentFrame!!) + } + updateTextures(mCurrentFrame!!) + Matrix.setIdentityM(mScaleMatrix, 0) + var scaleX = 1.0f + var scaleY = 1.0f + val ratio = (mCurrentFrame!!.width.toFloat() + / mCurrentFrame!!.height) + val vratio = mViewportWidth.toFloat() / mViewportHeight + if (mVideoFitEnabled) { + if (ratio > vratio) { + scaleY = vratio / ratio + } else { + scaleX = ratio / vratio + } + } else { + if (ratio < vratio) { + scaleY = vratio / ratio + } else { + scaleX = ratio / vratio + } + } + Matrix.scaleM(mScaleMatrix, 0, + scaleX * if (mCurrentFrame!!.isMirroredX) -1.0f else 1.0f, + scaleY, 1f) + metadataListener?.onMetadataReady(mCurrentFrame!!.metadata) + val mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, + "uMVPMatrix") + GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, + mScaleMatrix, 0) + GLES20.glDrawElements(GLES20.GL_TRIANGLES, mVertexIndex.size, + GLES20.GL_UNSIGNED_SHORT, mDrawListBuffer) + } else { + //black frame when video is disabled + gl.glClearColor(0f, 0f, 0f, 1f) + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT) + } + mFrameLock.unlock() + } + + fun displayFrame(frame: Frame?) { + mFrameLock.lock() + if (mCurrentFrame != null) { + mCurrentFrame!!.recycle() + } + mCurrentFrame = frame + mFrameLock.unlock() + } + + fun disableVideo(b: Boolean) { + mFrameLock.lock() + mVideoDisabled = b + if (mVideoDisabled) { + if (mCurrentFrame != null) { + mCurrentFrame!!.recycle() + } + mCurrentFrame = null + } + mFrameLock.unlock() + } + + fun enableVideoFit(enableVideoFit: Boolean) { + mVideoFitEnabled = enableVideoFit + } + + companion object { + // number of coordinates per vertex in this array + const val COORDS_PER_VERTEX = 3 + const val TEXTURECOORDS_PER_VERTEX = 2 + var mXYZCoords = floatArrayOf( + -1.0f, 1.0f, 0.0f, // top left + -1.0f, -1.0f, 0.0f, // bottom left + 1.0f, -1.0f, 0.0f, // bottom right + 1.0f, 1.0f, 0.0f // top right + ) + var mUVCoords = floatArrayOf(0f, 0f, 0f, 1f, 1f, 1f, 1f, 0f) + fun initializeTexture(name: Int, id: Int, width: Int, height: Int) { + GLES20.glActiveTexture(name) + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, id) + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST.toFloat()) + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR.toFloat()) + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE.toFloat()) + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE.toFloat()) + GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, + width, height, 0, GLES20.GL_LUMINANCE, + GLES20.GL_UNSIGNED_BYTE, null) + } + + fun loadShader(type: Int, shaderCode: String?): Int { + val shader = GLES20.glCreateShader(type) + GLES20.glShaderSource(shader, shaderCode) + GLES20.glCompileShader(shader) + return shader + } + } + + init { + val bb = ByteBuffer.allocateDirect(mXYZCoords.size * 4) + bb.order(ByteOrder.nativeOrder()) + mVertexBuffer = bb.asFloatBuffer() + mVertexBuffer.put(mXYZCoords) + mVertexBuffer.position(0) + val tb = ByteBuffer.allocateDirect(mUVCoords.size * 4) + tb.order(ByteOrder.nativeOrder()) + mTextureBuffer = tb.asFloatBuffer() + mTextureBuffer.put(mUVCoords) + mTextureBuffer.position(0) + val dlb = ByteBuffer.allocateDirect(mVertexIndex.size * 2) + dlb.order(ByteOrder.nativeOrder()) + mDrawListBuffer = dlb.asShortBuffer() + mDrawListBuffer.put(mVertexIndex) + mDrawListBuffer.position(0) + } + } + + override fun onFrame(frame: Frame) { + mRenderer.displayFrame(frame) + mView.requestRender() + } + + override fun setStyle(key: String, value: String) { + if (STYLE_VIDEO_SCALE == key) { + if (STYLE_VIDEO_FIT == value) { + mRenderer.enableVideoFit(true) + } else if (STYLE_VIDEO_FILL == value) { + mRenderer.enableVideoFit(false) + } + } + } + + override fun onVideoPropertiesChanged(videoEnabled: Boolean) { + mRenderer.disableVideo(!videoEnabled) + } + + override fun getView(): View { + return mView + } + + override fun onPause() { + mView.onPause() + } + + override fun onResume() { + mView.onResume() + } + + companion object { + private const val THUMBNAIL_SIZE = 90 //in dp + } + + init { + mView.setEGLContextClientVersion(2) + mView.setEGLConfigChooser(8, 8, 8, 8, 16, 0) + mView.holder.setFormat(PixelFormat.TRANSLUCENT) + mRenderer = MyRenderer() + mView.setRenderer(mRenderer) + mView.renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY + } +} diff --git a/android/app/src/main/res/layout/activity_video_call.xml b/android/app/src/main/res/layout/activity_video_call.xml index a7470da3..a54e57b3 100644 --- a/android/app/src/main/res/layout/activity_video_call.xml +++ b/android/app/src/main/res/layout/activity_video_call.xml @@ -97,8 +97,7 @@ android:layout_alignParentTop="true" android:layout_alignParentEnd="true" android:layout_marginTop="@dimen/local_preview_margin_top" - android:layout_marginEnd="@dimen/local_preview_margin_top" - android:background="@color/localBackground"> + android:layout_marginEnd="@dimen/local_preview_margin_top"> + + Date: Thu, 17 Jun 2021 15:43:49 +0300 Subject: [PATCH 27/30] fix disconected stream --- .../kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt index 5b98b27d..d89c939c 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt @@ -371,6 +371,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session Log.d(TAG, "onDisconnected: disconnected from session " + session.sessionId) mSession = null cmTimer.stop() + disconnectSession() } override fun onError(session: Session, opentokError: OpentokError) { @@ -477,11 +478,6 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session mSession!!.subscribe(mSubscriber) } - override fun dismiss() { - videoCallResponseListener?.onCallFinished(1000) - super.dismiss() - } - private fun disconnectSession() { if (mSession == null) { videoCallResponseListener?.onCallFinished(Activity.RESULT_CANCELED) @@ -489,6 +485,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session dialog?.dismiss() return } + if (mSubscriber != null) { mSubscriberViewContainer.removeView(mSubscriber!!.view) mSession!!.unsubscribe(mSubscriber) @@ -503,6 +500,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session } mSession!!.disconnect() countDownTimer?.cancel() + videoCallPresenter.callChangeCallStatus(ChangeCallStatusRequestModel(16, sessionStatusModel!!.doctorId, sessionStatusModel!!.generalid, token, sessionStatusModel!!.vcid)) dialog?.dismiss() } From 02f43d6e61ed77836baa035df98f108b96d23431 Mon Sep 17 00:00:00 2001 From: mosazaid Date: Thu, 17 Jun 2021 16:08:03 +0300 Subject: [PATCH 28/30] fix timer issue --- .../kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt | 4 +++- .../profile/profile_screen/patient_profile_screen.dart | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt index d89c939c..be1ed9d4 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt @@ -365,6 +365,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session cmTimer.base = SystemClock.elapsedRealtime() } cmTimer.start() + videoCallResponseListener?.minimizeVideoEvent(true) } override fun onDisconnected(session: Session) { @@ -372,6 +373,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session mSession = null cmTimer.stop() disconnectSession() + videoCallResponseListener?.minimizeVideoEvent(false) } override fun onError(session: Session, opentokError: OpentokError) { @@ -581,7 +583,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session btnMinimize.setImageResource(res) setViewsVisibility() - videoCallResponseListener?.minimizeVideoEvent(!isFullScreen) +// videoCallResponseListener?.minimizeVideoEvent(!isFullScreen) } private fun setViewsVisibility() { 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 ea61359a..0a05b596 100644 --- a/lib/screens/patients/profile/profile_screen/patient_profile_screen.dart +++ b/lib/screens/patients/profile/profile_screen/patient_profile_screen.dart @@ -105,7 +105,7 @@ class _PatientProfileScreenState extends State StreamSubscription callTimer; callConnected(){ - callTimer = CountdownTimer(Duration(minutes: 1), Duration(seconds: 1)).listen(null) + callTimer = CountdownTimer(Duration(minutes: 90), Duration(seconds: 1)).listen(null) ..onDone(() { callTimer.cancel(); }) From c585cbb5be345162bb171e34bcd352f9f4c2c181 Mon Sep 17 00:00:00 2001 From: mosazaid Date: Thu, 17 Jun 2021 16:35:43 +0300 Subject: [PATCH 29/30] fix timer issue --- .../hmg/hmgDr/ui/fragment/VideoCallFragment.kt | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt index be1ed9d4..35e28b23 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt @@ -546,16 +546,27 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session 400, 600 ) - (mSubscriber!!.renderer as DynamicVideoRenderer).enableThumbnailCircle(false) } else { dialog?.window?.setLayout( 300, 300 ) - (mSubscriber!!.renderer as DynamicVideoRenderer).enableThumbnailCircle(true) } isCircle = !isCircle + if (mSubscriber != null) { + (mSubscriber!!.renderer as DynamicVideoRenderer).enableThumbnailCircle(isCircle) + } else { + if (isCircle) { + videoCallContainer.background = ContextCompat.getDrawable(requireContext(), R.drawable.circle_shape) + mSubscriberViewContainer.background = ContextCompat.getDrawable(requireContext(), R.drawable.circle_shape) + } else { + videoCallContainer.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.text_color)) + mSubscriberViewContainer.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.text_color)) + } + + } + if (isCircle) { controlPanel.visibility = View.GONE layoutMini.visibility = View.GONE From 61c94f85b271affbeb16ba41ec10b3c7ee5a1982 Mon Sep 17 00:00:00 2001 From: Mohammad Aljammal Date: Thu, 17 Jun 2021 16:47:31 +0300 Subject: [PATCH 30/30] fix the end call --- lib/core/service/VideoCallService.dart | 8 +-- .../viewModel/patient-referral-viewmodel.dart | 2 +- lib/models/patient/patiant_info_model.dart | 11 ++-- lib/screens/live_care/end_call_screen.dart | 11 +++- .../patient_profile_screen.dart | 56 ++---------------- lib/screens/procedures/ProcedureCard.dart | 59 +------------------ 6 files changed, 28 insertions(+), 119 deletions(-) diff --git a/lib/core/service/VideoCallService.dart b/lib/core/service/VideoCallService.dart index 53739c9c..f72544a0 100644 --- a/lib/core/service/VideoCallService.dart +++ b/lib/core/service/VideoCallService.dart @@ -27,9 +27,9 @@ class VideoCallService extends BaseService{ this.patient = patientModel; DoctorProfileModel doctorProfile = await getDoctorProfile(isGetProfile: true); await VideoChannel.openVideoCallScreen( - kToken: "T1==cGFydG5lcl9pZD00NzI0Nzk1NCZzaWc9NGIyZDljOTY3YjFiNWU1YzUzNzFmMjIyNjJmNmEzY2Y5NzZjOTdlYzpzZXNzaW9uX2lkPTFfTVg0ME56STBOemsxTkg1LU1UWXlNekEyTlRRMU9EVXhObjVrVFRoMFlVdFJXaXRYTWpadFZGZHFhSGxZVGpOdE1UVi1mZyZjcmVhdGVfdGltZT0xNjIzMDY1NDk1Jm5vbmNlPTAuMjM2Mjk0NTIwMTkyOTA4OTcmcm9sZT1wdWJsaXNoZXImZXhwaXJlX3RpbWU9MTYyNTY1NzQ5NCZpbml0aWFsX2xheW91dF9jbGFzc19saXN0PQ==",//tartCallRes.openTokenID, - kSessionId: '1_MX40NzI0Nzk1NH5-MTYyMzA2NTQ1ODUxNn5kTTh0YUtRWitXMjZtVFdqaHlYTjNtMTV-fg',//startCallRes.openSessionID, - kApiKey: '47247954',//'46209962', + kToken: startCallRes.openTokenID,//"T1==cGFydG5lcl9pZD00NzI0Nzk1NCZzaWc9NGIyZDljOTY3YjFiNWU1YzUzNzFmMjIyNjJmNmEzY2Y5NzZjOTdlYzpzZXNzaW9uX2lkPTFfTVg0ME56STBOemsxTkg1LU1UWXlNekEyTlRRMU9EVXhObjVrVFRoMFlVdFJXaXRYTWpadFZGZHFhSGxZVGpOdE1UVi1mZyZjcmVhdGVfdGltZT0xNjIzMDY1NDk1Jm5vbmNlPTAuMjM2Mjk0NTIwMTkyOTA4OTcmcm9sZT1wdWJsaXNoZXImZXhwaXJlX3RpbWU9MTYyNTY1NzQ5NCZpbml0aWFsX2xheW91dF9jbGFzc19saXN0PQ==", + kSessionId:startCallRes.openSessionID,//1_MX40NzI0Nzk1NH5-MTYyMzA2NTQ1ODUxNn5kTTh0YUtRWitXMjZtVFdqaHlYTjNtMTV-fg + kApiKey: '46209962',//'47247954' vcId: patient.vcId, patientName: patient.fullName ?? (patient.firstName != null ? "${patient.firstName} ${patient.lastName}" : "-"), tokenID: await sharedPref.getString(TOKEN), @@ -80,7 +80,7 @@ class VideoCallService extends BaseService{ endCallReq.generalid = 'Cs2020@2016\$2958'; endCallReq.vCID = vCID; endCallReq.isDestroy = isPatient; - // await _liveCarePatientServices.endCall(endCallReq); + await _liveCarePatientServices.endCall(endCallReq); if (_liveCarePatientServices.hasError) { error = _liveCarePatientServices.error; } diff --git a/lib/core/viewModel/patient-referral-viewmodel.dart b/lib/core/viewModel/patient-referral-viewmodel.dart index 9e852978..0351db77 100644 --- a/lib/core/viewModel/patient-referral-viewmodel.dart +++ b/lib/core/viewModel/patient-referral-viewmodel.dart @@ -240,7 +240,7 @@ class PatientReferralViewModel extends BaseViewModel { patientID: patient.patientId, roomID: patient.roomId, referralClinic: clinicID, - admissionNo: patient.appointmentNo, + admissionNo: int.parse(patient.admissionNo), referralDoctor: doctorID, patientTypeID: patient.patientType, referringDoctorRemarks: remarks, diff --git a/lib/models/patient/patiant_info_model.dart b/lib/models/patient/patiant_info_model.dart index 71d46dc9..29dc6198 100644 --- a/lib/models/patient/patiant_info_model.dart +++ b/lib/models/patient/patiant_info_model.dart @@ -224,10 +224,13 @@ class PatiantInformtion { isSigned: json['isSigned'], medicationOrders: json['medicationOrders'], nationality: json['nationality'] ?? json['NationalityNameN'], - patientMRN: json['patientMRN'] ?? json['PatientMRN']?? ( - json["PatientID"] != null ? - int.parse(json["PatientID"].toString()) - : int.parse(json["patientID"].toString())), + patientMRN: json['patientMRN'] ?? + json['PatientMRN'] ?? + (json["PatientID"] != null + ? int?.parse(json["PatientID"].toString()) + : json["patientID"] != null ? int?.parse( + json["patientID"].toString()) : json["patientId"] != null ? int + ?.parse(json["patientId"].toString()) : ''), visitType: json['visitType'] ?? json['visitType'] ?? json['visitType'], nationalityFlagURL: json['NationalityFlagURL'] ?? json['NationalityFlagURL'], diff --git a/lib/screens/live_care/end_call_screen.dart b/lib/screens/live_care/end_call_screen.dart index d355473d..d3db12b6 100644 --- a/lib/screens/live_care/end_call_screen.dart +++ b/lib/screens/live_care/end_call_screen.dart @@ -23,9 +23,9 @@ import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:hexcolor/hexcolor.dart'; class EndCallScreen extends StatefulWidget { + final PatiantInformtion patient; - - const EndCallScreen({Key key,}) : super(key: key); + const EndCallScreen({Key key, this.patient,}) : super(key: key); @override _EndCallScreenState createState() => _EndCallScreenState(); @@ -42,11 +42,18 @@ class _EndCallScreenState extends State { String to; LiveCarePatientViewModel liveCareModel; + @override + void initState() { + super.initState(); + if(widget.patient!=null) + patient = widget.patient; + } @override void didChangeDependencies() { super.didChangeDependencies(); final routeArgs = ModalRoute.of(context).settings.arguments as Map; + if(routeArgs.containsKey('patient')) patient = routeArgs['patient']; } 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 ea61359a..03def2f7 100644 --- a/lib/screens/patients/profile/profile_screen/patient_profile_screen.dart +++ b/lib/screens/patients/profile/profile_screen/patient_profile_screen.dart @@ -339,19 +339,18 @@ class _PatientProfileScreenState extends State if(isCallFinished) { - // Navigator.push(context, MaterialPageRoute( - // builder: (BuildContext context) => EndCallScreen(patient:patient))); - var asd = ""; + Navigator.push(context, MaterialPageRoute( + builder: (BuildContext context) => EndCallScreen(patient:patient))); } else { GifLoaderDialogUtils.showMyDialog(context); - // await model.startCall( isReCall : false, vCID: patient.vcId); + await model.startCall( isReCall : false, vCID: patient.vcId); if(model.state == ViewState.ErrorLocal) { GifLoaderDialogUtils.hideDialog(context); Helpers.showErrorToast(model.error); } else { await model.getDoctorProfile(); - // patient.appointmentNo = model.startCallRes.appointmentNo; + patient.appointmentNo = model.startCallRes.appointmentNo; patient.episodeNo = 0; GifLoaderDialogUtils.hideDialog(context); @@ -360,53 +359,6 @@ class _PatientProfileScreenState extends State }); - // await VideoChannel.openVideoCallScreen( - // kToken: "T1==cGFydG5lcl9pZD00NzI0Nzk1NCZzaWc9NGIyZDljOTY3YjFiNWU1YzUzNzFmMjIyNjJmNmEzY2Y5NzZjOTdlYzpzZXNzaW9uX2lkPTFfTVg0ME56STBOemsxTkg1LU1UWXlNekEyTlRRMU9EVXhObjVrVFRoMFlVdFJXaXRYTWpadFZGZHFhSGxZVGpOdE1UVi1mZyZjcmVhdGVfdGltZT0xNjIzMDY1NDk1Jm5vbmNlPTAuMjM2Mjk0NTIwMTkyOTA4OTcmcm9sZT1wdWJsaXNoZXImZXhwaXJlX3RpbWU9MTYyNTY1NzQ5NCZpbml0aWFsX2xheW91dF9jbGFzc19saXN0PQ==",//model.startCallRes.openTokenID, - // kSessionId:"1_MX40NzI0Nzk1NH5-MTYyMzA2NTQ1ODUxNn5kTTh0YUtRWitXMjZtVFdqaHlYTjNtMTV-fg",// model.startCallRes.openSessionID, - // kApiKey: '47247954',//46209962 - // vcId: patient.vcId, - // patientName: patient.fullName ?? (patient.firstName != null ? "${patient.firstName} ${patient.lastName}" : "-"), - // tokenID: await model.getToken(), - // generalId: GENERAL_ID, - // doctorId: model.doctorProfile.doctorID, - // onFailure: (String error) { - // DrAppToastMsg.showErrorToast(error); - // },onCallConnected: callConnected, - // onCallEnd: () { - // var asd=""; - // WidgetsBinding.instance.addPostFrameCallback((_) { - // GifLoaderDialogUtils.showMyDialog(context); - // model.endCall(patient.vcId, false,).then((value) { - // GifLoaderDialogUtils.hideDialog(context); - // if (model.state == ViewState.ErrorLocal) { - // DrAppToastMsg.showErrorToast(model.error); - // } - // setState(() { - // isCallFinished = true; - // }); - // }); - // }); - // Navigator.push(context, MaterialPageRoute( - // builder: (BuildContext context) => - // EndCallScreen(patient:patient))); - // }, - // onCallNotRespond: (SessionStatusModel sessionStatusModel) { - // var asd=""; - // // WidgetsBinding.instance.addPostFrameCallback((_) { - // // GifLoaderDialogUtils.showMyDialog(context); - // // model.endCall(patient.vcId, sessionStatusModel.sessionStatus == 3,).then((value) { - // // GifLoaderDialogUtils.hideDialog(context); - // // if (model.state == ViewState.ErrorLocal) { - // // DrAppToastMsg.showErrorToast(model.error); - // // } - // // setState(() { - // // isCallFinished = true; - // // }); - // // }); - // // - // // }); - // }); - } } diff --git a/lib/screens/procedures/ProcedureCard.dart b/lib/screens/procedures/ProcedureCard.dart index d998ca58..31611b67 100644 --- a/lib/screens/procedures/ProcedureCard.dart +++ b/lib/screens/procedures/ProcedureCard.dart @@ -18,7 +18,7 @@ class ProcedureCard extends StatelessWidget { final int categoryID; final PatiantInformtion patient; final int doctorID; - + final bool isInpatient; const ProcedureCard({ Key key, this.onTap, @@ -26,7 +26,7 @@ class ProcedureCard extends StatelessWidget { this.categoryID, this.categoryName, this.patient, - this.doctorID, + this.doctorID, this.isInpatient = false, }) : super(key: key); @override @@ -132,36 +132,6 @@ class ProcedureCard extends StatelessWidget { ), ], ), - // Row( - // children: [ - // AppText( - // TranslationBase.of(context).doctorName + ": ", - // //color: Colors.grey, - // fontSize: 12, - // color: Colors.grey, - // ), - // AppText( - // entityList.doctorName.toString(), - // fontSize: 12, - // bold: true, - // ), - // ], - // ), - // Row( - // children: [ - // AppText( - // TranslationBase.of(context).clinic + ": ", - // //color: Colors.grey, - // fontSize: 12, - // color: Colors.grey, - // ), - // AppText( - // entityList.clinicDescription ?? "", - // bold: true, - // fontSize: 12, - // ), - // ], - // ), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -222,27 +192,6 @@ class ProcedureCard extends StatelessWidget { ), ], ), - /*Container( - alignment: Alignment.centerRight, - child: InkWell( - onTap: () { - Navigator.push( - context, - FadePage( - page: FlowChartPage( - filterName: entityList.procedureName, - patient: patient, - ), - ), - ); - }, - child: AppText( - TranslationBase.of(context).showMoreBtn, - textDecoration: TextDecoration.underline, - color: Colors.blue, - ), - ), - ),*/ Padding( padding: const EdgeInsets.all(8.0), child: Row( @@ -254,9 +203,7 @@ class ProcedureCard extends StatelessWidget { fontSize: 12, ), ), - if ((entityList.categoryID == 2 || - entityList.categoryID == 4) && - doctorID == entityList.doctorID) + if ((entityList.categoryID == 2 || entityList.categoryID == 4) && doctorID == entityList.doctorID && !isInpatient) InkWell( child: Icon(DoctorApp.edit), onTap: onTap,