Ver Fonte

Conference call in progress

kevin há 6 meses atrás
pai
commit
02760fbc91

+ 3 - 0
ExampleCode/ExampleCode/ViewController.swift

@@ -48,6 +48,9 @@ class ViewController: UIViewController, ConnectDelegate {
             UIAction(title: "Settings".localized(), handler: {(_) in
                 APIS.openSetting()
             }),
+            UIAction(title: "Conference Call".localized(), handler: {(_) in
+                APIS.openConference()
+            }),
         ]
         self.navigationController?.navigationBar.backgroundColor = .mainColor
         

+ 8 - 0
ExampleCode/Podfile

@@ -8,4 +8,12 @@ target 'ExampleCode' do
   pod 'StreamShield', :path => '../StreamShield'
   #pod 'NexilisUC', :path => '../NexilisUC'
 
+  post_install do |installer|
+    installer.pods_project.targets.each do |target|
+      target.build_configurations.each do |config|
+        config.build_settings["IPHONEOS_DEPLOYMENT_TARGET"] = "14.0"
+      end
+    end
+  end
+
 end

+ 21 - 0
NexilisLite/NexilisLite/Resource/Assets.xcassets/pb_vroom.imageset/Contents.json

@@ -0,0 +1,21 @@
+{
+  "images" : [
+    {
+      "filename" : "pb_vroom.png",
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
NexilisLite/NexilisLite/Resource/Assets.xcassets/pb_vroom.imageset/pb_vroom.png


+ 17 - 2
NexilisLite/NexilisLite/Resource/Palio.storyboard

@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23094" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
     <device id="retina6_1" orientation="portrait" appearance="light"/>
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23084"/>
         <capability name="Image references" minToolsVersion="12.0"/>
         <capability name="Safe area layout guides" minToolsVersion="9.0"/>
         <capability name="System colors in document resources" minToolsVersion="11.0"/>
@@ -3289,6 +3289,21 @@
             </objects>
             <point key="canvasLocation" x="8525" y="1550"/>
         </scene>
+        <!--Video Conference View Controller-->
+        <scene sceneID="6Ra-G8-z6h">
+            <objects>
+                <viewController storyboardIdentifier="videoConfRoom" useStoryboardIdentifierAsRestorationIdentifier="YES" id="aNN-LV-N5R" customClass="VideoConferenceViewController" customModule="NexilisLite" customModuleProvider="target" sceneMemberID="viewController">
+                    <view key="view" contentMode="scaleToFill" id="DIf-G2-adx">
+                        <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <viewLayoutGuide key="safeArea" id="2bY-HH-PHR"/>
+                        <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="vma-8o-eO8" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="9367" y="1550"/>
+        </scene>
         <!--Navigation Controller-->
         <scene sceneID="QZB-X2-3Cl">
             <objects>

+ 14 - 14
NexilisLite/NexilisLite/Source/APIS.swift

@@ -251,20 +251,20 @@ public class APIS: NSObject {
         }
     }
     
-//    public static func openSeminar() {
-//        let isChangeProfile = Utils.getSetProfile()
-//        if !isChangeProfile {
-//            APIS.showChangeProfile()
-//            return
-//        }
-//        let navigationController = CustomNavigationController(rootViewController: CreateSeminarViewController())
-//        navigationController.defaultStyle()
-//        if UIApplication.shared.visibleViewController?.navigationController != nil {
-//            UIApplication.shared.visibleViewController?.navigationController?.present(navigationController, animated: true, completion: nil)
-//        } else {
-//            UIApplication.shared.visibleViewController?.present(navigationController, animated: true, completion: nil)
-//        }
-//    }
+    public static func openConference() {
+        let isChangeProfile = Utils.getSetProfile()
+        if !isChangeProfile {
+            APIS.showChangeProfile()
+            return
+        }
+        let navigationController = CustomNavigationController(rootViewController: CreateSeminarViewController())
+        navigationController.defaultStyle()
+        if UIApplication.shared.visibleViewController?.navigationController != nil {
+            UIApplication.shared.visibleViewController?.navigationController?.present(navigationController, animated: true, completion: nil)
+        } else {
+            UIApplication.shared.visibleViewController?.present(navigationController, animated: true, completion: nil)
+        }
+    }
     
     public static func openAudioCall() {
         let isChangeProfile = Utils.getSetProfile()

+ 1 - 1
NexilisLite/NexilisLite/Source/CoreMessage_TMessageBank.swift

@@ -1258,7 +1258,7 @@ public class CoreMessage_TMessageBank {
         tmessage.mPIN = me
         tmessage.mStatus = CoreMessage_TMessageUtil.getTID()
         tmessage.mBodies[CoreMessage_TMessageKey.F_PIN] = me
-        tmessage.mBodies[CoreMessage_TMessageKey.BROADCAST_ID] = blog_id
+        tmessage.mBodies[CoreMessage_TMessageKey.BROADCAST_ID] = me +  CoreMessage_TMessageUtil.getTID()
         tmessage.mBodies[CoreMessage_TMessageKey.START_DATE] = time
         return tmessage
     }

+ 3 - 0
NexilisLite/NexilisLite/Source/CoreMessage_TMessageCode.swift

@@ -639,6 +639,9 @@ public class CoreMessage_TMessageCode {
     public static let VC_ROOM_END = "VCR2";
     public static let VC_ROOM_WHITEBOARD_IMAGE = "VCR3";
     public static let VC_ROOM_DRAW = "VCR4";
+    public static let VC_ROOM_INVITE = "VCR5";
+    public static let VC_ROOM_GET_MEMBERS = "VCR6";
+    public static let VC_ROOM_FACE_DETECTION = "VCR11";
     
     //Signup-Signin MSISDN dan OTP
     public static let VERIFY_OTP = "VOTP";

+ 2 - 0
NexilisLite/NexilisLite/Source/Utils.swift

@@ -417,6 +417,8 @@ public final class Utils {
             return showNSMutableAttributedString(("📄 " + "Live Streaming".localized()))
         } else if chat.attachmentFlag == "26" {
             return showNSMutableAttributedString(("📄 " + "Seminar".localized()))
+        } else if chat.attachmentFlag == "25" {
+            return showNSMutableAttributedString("📄 " + "Video Conference Room".localized())
         } else if !chat.audio.isEmpty {
             return showNSMutableAttributedString(("♫ " + "Audio".localized()))
         } else if !chat.image.isEmpty {

+ 586 - 0
NexilisLite/NexilisLite/Source/View/Call/CreateConferenceCallController.swift

@@ -0,0 +1,586 @@
+//
+//  CreateSeminarViewController.swift
+//  NexilisLite
+//
+//  Created by Maronakins on 31/05/23.
+//
+
+import UIKit
+import NotificationBannerSwift
+import AVFoundation
+
+public class CreateConferenceCallController: UITableViewController {
+    
+    var isJoin = false
+    
+    var data: [String: Any] = [:]
+    
+    private enum Section {
+        case chooser
+        case title
+        case description
+        case users
+        case groups
+    }
+    
+    private var sections: [Section] = [
+        .chooser,
+        .title,
+        .description
+    ]
+    
+    private var chooser: [ConfChooser] = [
+        ConfChooser(title: "Target Audience".localized(), value: AudienceViewController().data.first),
+        ConfChooser(title: "Promotion Type".localized(), value: TypeViewController().data.first)
+    ]
+    
+    private var users: [User] = [] {
+        didSet {
+            DispatchQueue.main.async { [weak self] in
+                self?.tableView.reloadData()
+            }
+        }
+    }
+    
+    private var groups: [Group] = [] {
+        didSet {
+            DispatchQueue.main.async { [weak self] in
+                self?.tableView.reloadData()
+            }
+        }
+    }
+    
+    private let cellIdentifier = "reuseIdentifier"
+    
+    lazy var table: UITableView = {
+        let tableView = UITableView(frame: CGRect.zero, style: .grouped)
+        tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellIdentifier)
+        return tableView
+    }()
+    
+    lazy var titleView: UITextField = {
+        let textField = UITextField()
+        textField.borderStyle = .none
+        textField.placeholder = "Title".localized()
+        return textField
+    }()
+    
+    lazy var descriptionView: UITextView = {
+        let textView = UITextView()
+        textView.text = "Description".localized()
+        textView.textColor = UIColor.lightGray
+        return textView
+    }()
+    
+    deinit {
+        //print(#function, ">>>> TADAA1")
+        NotificationCenter.default.removeObserver(self)
+        NotificationCenter.default.post(name: NSNotification.Name(rawValue: "refreshView"), object: nil, userInfo: nil)
+    }
+    
+    public override func viewDidLoad() {
+        super.viewDidLoad()
+        
+        title = "Conference Call".localized()
+        
+        navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(didTapCancel(sender:)))
+        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Start".localized(), style: .plain, target: self, action: #selector(didTapRight(sender:)))
+        
+        descriptionView.delegate = self
+        
+        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
+        tapGesture.cancelsTouchesInView = false
+        table.addGestureRecognizer(tapGesture)
+        
+        if isJoin {
+            navigationItem.rightBarButtonItem?.title = "Join".localized()
+            titleView.isEnabled = false
+            descriptionView.isEditable = false
+            if let a = data["type"] as? String {
+                chooser[0].value = AudienceViewController().data[getTypeIndex(value: a)]
+            }
+            if let b = data["broadcast_type"] as? String {
+                chooser[1].value = TypeViewController().data[getBroadcastIndex(value: b)]
+            }
+            if let c = data["title"] as? String {
+                titleView.text = c
+            }
+            if let d = data["description"] as? String {
+                descriptionView.text = d
+            }
+        } else if !isJoin && !data.isEmpty {
+            navigationItem.rightBarButtonItem?.title = "Start".localized()
+            titleView.isEnabled = false
+            descriptionView.isEditable = false
+            if let a = data["type"] as? String {
+                chooser[0].value = AudienceViewController().data[getTypeIndex(value: a)]
+            }
+            if let b = data["broadcast_type"] as? String {
+                chooser[1].value = TypeViewController().data[getBroadcastIndex(value: b)]
+            }
+            if let c = data["title"] as? String {
+                titleView.text = c
+            }
+            if let d = data["description"] as? String {
+                descriptionView.text = d
+            }
+        }
+        
+        tableView = table
+    }
+    
+    @objc func dismissKeyboard() {
+        titleView.resignFirstResponder()
+        descriptionView.resignFirstResponder()
+    }
+    
+    @objc func didTapCancel(sender: AnyObject) {
+        navigationController?.dismiss(animated: true, completion: nil)
+    }
+    
+    @objc func didTapRight(sender: Any?) {
+        let controller = SeminarViewController()
+        controller.isLive = !isJoin
+        if isJoin {
+            guard let by = data["by"] as? String else {
+                return
+            }
+            let dataBlog = data["blog"] as? String
+//            if dataBlog != nil {
+//                if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.getIsInitiatorJoin(p_broadcaster: by, p_category: "3", blog_id: dataBlog!)) {
+//                    if response.getBody(key: CoreMessage_TMessageKey.ERRCOD) != "00" {
+//                        let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
+//                        imageView.tintColor = .white
+//                        let banner = FloatingNotificationBanner(title: "Seminar session hasn\'t started yet".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
+//                        banner.show()
+//                        return
+//                    }
+//                } else {
+//                    let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
+//                    imageView.tintColor = .white
+//                    let banner = FloatingNotificationBanner(title: "No Network. Please try again.".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
+//                    banner.show()
+//                    return
+//                }
+//            }
+            controller.data = by
+            controller.streamingData = data
+        } else {
+            let goAudioCall = Nexilis.checkMicPermission()
+            if !goAudioCall {
+                let alert = LibAlertController(title: "Attention!".localized(), message: "Please allow microphone permission in your settings".localized(), preferredStyle: .alert)
+                alert.addAction(UIAlertAction(title: "OK".localized(), style: UIAlertAction.Style.default, handler: { _ in
+                    if let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) {
+                        UIApplication.shared.open(url, options: [:], completionHandler: nil)
+                    }
+                }))
+                if UIApplication.shared.visibleViewController?.navigationController != nil {
+                    UIApplication.shared.visibleViewController?.navigationController?.present(alert, animated: true, completion: nil)
+                } else {
+                    UIApplication.shared.visibleViewController?.present(alert, animated: true, completion: nil)
+                }
+                return
+            }
+            var permissionCheck = -1
+            if AVCaptureDevice.authorizationStatus(for: .video) ==  .authorized {
+                permissionCheck = 1
+            } else if AVCaptureDevice.authorizationStatus(for: .video) ==  .denied {
+                permissionCheck = 0
+            } else {
+                AVCaptureDevice.requestAccess(for: .video, completionHandler: { (granted: Bool) -> Void in
+                    if granted == true {
+                        permissionCheck = 1
+                    } else {
+                        permissionCheck = 0
+                    }
+                })
+            }
+            
+            while permissionCheck == -1 {
+                sleep(1)
+            }
+            
+            if permissionCheck == 0 {
+                let alert = LibAlertController(title: "Attention!".localized(), message: "Please allow camera permission in your settings".localized(), preferredStyle: .alert)
+                alert.addAction(UIAlertAction(title: "OK".localized(), style: UIAlertAction.Style.default, handler: { _ in
+                    if let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) {
+                        UIApplication.shared.open(url, options: [:], completionHandler: nil)
+                    }
+                }))
+                if UIApplication.shared.visibleViewController?.navigationController != nil {
+                    UIApplication.shared.visibleViewController?.navigationController?.present(alert, animated: true, completion: nil)
+                } else {
+                    UIApplication.shared.visibleViewController?.present(alert, animated: true, completion: nil)
+                }
+                return
+            }
+            var data: [String: Any] = [:]
+            if !isJoin && !self.data.isEmpty {
+                data = self.data
+            } else if self.data.isEmpty {
+                guard let streamingTitle = titleView.text else {
+                    return
+                }
+                guard let streamingDesc = descriptionView.text else {
+                    return
+                }
+                
+                let id = "LST\(Date().currentTimeMillis().toHex())"
+                
+                data["title"] = streamingTitle
+                data["description"] = streamingDesc
+                data["by"] = User.getMyPin() ?? ""
+                data["time"] = Date().currentTimeMillis()
+                data["blog"] = id
+                
+                if streamingTitle.trimmingCharacters(in: .whitespaces).isEmpty {
+                    let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
+                    imageView.tintColor = .white
+                    let banner = FloatingNotificationBanner(title: "Seminar title can't be empty".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
+                    banner.show()
+                    return
+                } else if streamingDesc.trimmingCharacters(in: .whitespaces).isEmpty {
+                    let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
+                    imageView.tintColor = .white
+                    let banner = FloatingNotificationBanner(title: "Seminar description can't be empty".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
+                    banner.show()
+                    return
+                }
+                
+                var type: String = "0" // Friend
+                let groups: [String] = groups.map{ $0.id }
+                let members: [String] = users.map{ $0.pin }
+                switch chooser[0].id {
+                case 0:
+                    type = "7"
+                case 1:
+                    type = "8"
+                case 2:
+                    type = "3"
+                case 3:
+                    type = "6"
+                case 4:
+                    type = "5"
+                default:
+                    type = "0"
+                }
+                
+                data["groups"] = groups
+                data["members"] = members
+                data["type"] = type
+                
+                var notif: String = "0"
+                switch chooser[1].id {
+                case 0:
+                    notif = "1"
+                default:
+                    notif = "2"
+                }
+                
+                data["broadcast_type"] = notif
+                guard let json = String(data: try! JSONSerialization.data(withJSONObject: data, options: []), encoding: String.Encoding.utf8) else {
+                    return
+                }
+                
+                if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.createSeminar(title: "1~\(data["title"] ?? "")", type: data["type"] as! String, category: "4", notifType: data["broadcast_type"] as! String, blogId: data["blog"] as! String, data: json)) {
+                    if response.getBody(key: CoreMessage_TMessageKey.ERRCOD) != "00" {
+                        let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
+                        imageView.tintColor = .white
+                        let banner = FloatingNotificationBanner(title: "Server Busy. Please try again.".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
+                        banner.show()
+                        return
+                    }
+                } else {
+                    let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
+                    imageView.tintColor = .white
+                    let banner = FloatingNotificationBanner(title: "No Network. Please try again.".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
+                    banner.show()
+                    return
+                }
+                
+//                Nexilis.saveMessageBot(textMessage: json, blog_id: data["blog"] as? String ?? "", attachment_type: "26")
+                NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
+            }
+            controller.data = User.getMyPin()!
+            controller.streamingData = data
+        }
+        navigationController?.show(controller, sender: nil)
+    }
+    
+    private func getTypeIndex(value: String) -> Int {
+        var type = 0
+        switch value {
+        case "7":
+            type = 0
+        case "8":
+            type = 1
+        case "3":
+            type = 2
+        case "6":
+            type = 3
+        case "5":
+            type = 4
+        default:
+            type = 0
+        }
+        return type
+    }
+    
+    private func getBroadcastIndex(value: String) -> Int {
+        var notif = 0
+        switch value {
+        case "1":
+            notif = 0
+        default:
+            notif = 1
+        }
+        return notif
+    }
+    
+    // MARK: - Table view data source
+    
+    public override func numberOfSections(in tableView: UITableView) -> Int {
+        return sections.count
+    }
+    
+    public override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        switch sections[section] {
+        case .chooser:
+            return 2
+        case .users:
+            return users.count + 1
+        case .groups:
+            return groups.count + 1
+        default:
+            return 1
+        }
+    }
+    
+    public override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
+        switch sections[indexPath.section] {
+        case .description:
+            return 100
+        default:
+            return 44
+        }
+    }
+    
+    public override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
+        if isJoin, sections[indexPath.section] == .chooser {
+            return nil
+        }
+        return indexPath
+    }
+    
+    public override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        tableView.deselectRow(at: indexPath, animated: true)
+        switch sections[indexPath.section] {
+        case .chooser:
+            if indexPath.row == 0 {
+                if isJoin || (!isJoin && !data.isEmpty){
+                    return
+                }
+                let chooser = chooser[indexPath.row]
+                let controller = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "audienceView") as AudienceViewController
+                controller.selected = chooser.value
+                controller.isDismiss = { [weak self] index in
+                    chooser.id = index
+                    chooser.value = AudienceViewController().data[index]
+                    guard let sec = self?.sections else {
+                        return
+                    }
+                    if sec.count > 4 {
+                        self?.sections.removeLast()
+                    }
+                    if chooser.value == "Group".localized() {
+                        self?.sections.append(.groups)
+                        if self?.users.count != 0 {
+                            self?.users.removeAll()
+                        }
+                    } else if chooser.value == "User".localized() {
+                        self?.sections.append(.users)
+                        if self?.groups.count != 0{
+                            self?.groups.removeAll()
+                        }
+                    } else {
+                        if self?.users.count != 0 {
+                            self?.users.removeAll()
+                        } else if self?.groups.count != 0{
+                            self?.groups.removeAll()
+                        }
+                    }
+                    DispatchQueue.main.async {
+                        tableView.reloadData()
+                    }
+                }
+                navigationController?.show(controller, sender: nil)
+            } else if indexPath.row == 1 {
+                if isJoin || (!isJoin && !data.isEmpty){
+                    return
+                }
+                let chooser = chooser[indexPath.row]
+                let controller = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "typeView") as TypeViewController
+                controller.selected = chooser.value
+                controller.isDismiss = { index in
+                    chooser.id = index
+                    chooser.value = TypeViewController().data[index]
+                    DispatchQueue.main.async {
+                        tableView.reloadRows(at: [indexPath], with: .automatic)
+                    }
+                }
+                navigationController?.show(controller, sender: nil)
+            }
+        case .users:
+            if indexPath.row == 0 {
+                let controller = QmeraUserChooserViewController()
+                controller.ignored.append(contentsOf: users)
+                controller.isDismiss = { users in
+                    self.users.append(contentsOf: users)
+                }
+                navigationController?.show(controller, sender: nil)
+            }
+        case .groups:
+            if indexPath.row == 0 {
+                let controller = QmeraGroupStreamingViewController()
+                controller.ignored.append(contentsOf: groups)
+                controller.isDismiss = { groups in
+                    self.groups.append(contentsOf: groups)
+                }
+                navigationController?.show(controller, sender: nil)
+            }
+        default:
+            return
+        }
+    }
+    
+    public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
+        cell.contentView.subviews.forEach{ NSLayoutConstraint.deactivate($0.constraints); $0.removeFromSuperview() }
+        cell.contentConfiguration = nil
+        cell.accessoryType = .none
+        cell.selectionStyle = .none
+        switch sections[indexPath.section] {
+        case .chooser:
+            var content = cell.defaultContentConfiguration()
+            let data = chooser[indexPath.row]
+            content.text = data.title
+            content.secondaryText = data.value
+            content.textProperties.font = UIFont.systemFont(ofSize: 14)
+            content.secondaryTextProperties.color = .systemGray
+            content.secondaryTextProperties.font = UIFont.systemFont(ofSize: 14)
+            content.prefersSideBySideTextAndSecondaryText = true
+            cell.contentConfiguration = content
+            cell.accessoryType = .disclosureIndicator
+            cell.selectionStyle = .default
+        case .title:
+            cell.contentView.addSubview(titleView)
+            titleView.anchor(top: cell.topAnchor, left: cell.leftAnchor, bottom: cell.bottomAnchor, right: cell.rightAnchor, paddingLeft: 20, paddingRight: 20)
+        case .description:
+            let stack = UIStackView()
+            stack.axis = .vertical
+            stack.distribution = .fill
+            cell.contentView.addSubview(stack)
+            stack.anchor(top: cell.topAnchor, left: cell.leftAnchor, bottom: cell.bottomAnchor, right: cell.rightAnchor, paddingTop: 8, paddingLeft: 20, paddingBottom: 8, paddingRight: 20)
+            stack.addArrangedSubview(descriptionView)
+        case .users:
+            var content = cell.defaultContentConfiguration()
+            if indexPath.row == 0 {
+                content.image = UIImage(systemName: "plus.circle.fill")
+                content.imageProperties.tintColor = .mainColor
+                content.text = "Add user".localized()
+                content.textProperties.font = UIFont.systemFont(ofSize: 14)
+                cell.accessoryType = .disclosureIndicator
+                cell.selectionStyle = .default
+            } else {
+                let data = users[indexPath.row - 1]
+                getImage(name: data.thumb, placeholderImage: UIImage(named: "Profile---Purple", in: Bundle.resourceBundle(for: Nexilis.self), with: nil), isCircle: true, tableView: tableView, indexPath: indexPath) { result, isDownloaded, image in
+                    content.image = image
+                }
+                content.text = data.fullName
+                content.textProperties.font = UIFont.systemFont(ofSize: 14)
+            }
+            cell.contentConfiguration = content
+        case .groups:
+            var content = cell.defaultContentConfiguration()
+            if indexPath.row == 0 {
+                content.image = UIImage(systemName: "plus.circle.fill")
+                content.imageProperties.tintColor = .mainColor
+                content.text = "Add group".localized()
+                content.textProperties.font = UIFont.systemFont(ofSize: 14)
+                cell.accessoryType = .disclosureIndicator
+                cell.selectionStyle = .default
+            } else {
+                let data = groups[indexPath.row - 1]
+                getImage(name: data.profile, placeholderImage: UIImage(named: "Conversation---Purple", in: Bundle.resourceBundle(for: Nexilis.self), with: nil), isCircle: true, tableView: tableView, indexPath: indexPath) { result, isDownloaded, image in
+                    content.image = image
+                }
+                content.text = data.name
+                content.textProperties.font = UIFont.systemFont(ofSize: 14)
+            }
+            cell.contentConfiguration = content
+        }
+        return cell
+    }
+    
+    public override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
+        switch sections[indexPath.section] {
+        case .users:
+            if (editingStyle == .delete) {
+                users.remove(at: indexPath.row - 1)
+                tableView.deleteRows(at: [indexPath], with: .automatic)
+            }
+        case .groups:
+            if (editingStyle == .delete) {
+                groups.remove(at: indexPath.row - 1)
+                tableView.deleteRows(at: [indexPath], with: .automatic)
+            }
+        default:
+            return
+        }
+    }
+    
+    public override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
+        if indexPath.section < 4 {
+            return .none
+        } else if indexPath.section == 4 && indexPath.row == 0 {
+            return .none
+        }
+
+        return .delete
+    }
+    
+    @IBAction func result(unwind segue: UIStoryboardSegue) {
+        
+    }
+    
+}
+
+private class ConfChooser {
+    
+    let title: String
+    var id: Int = 0
+    var value: String?
+    
+    init(title: String, id: Int = 0, value: String?) {
+        self.title = title
+        self.id = id
+        self.value = value
+    }
+    
+}
+
+extension CreateConferenceCallController: UITextViewDelegate {
+    public func textViewDidBeginEditing(_ textView: UITextView) {
+        if textView.textColor == UIColor.lightGray {
+            textView.text = nil
+            textView.textColor = UIColor.black
+        }
+    }
+    
+    public func textViewDidEndEditing(_ textView: UITextView) {
+        if textView.text.isEmpty {
+            textView.text = "Description".localized()
+            textView.textColor = UIColor.lightGray
+        }
+    }
+}

+ 1550 - 0
NexilisLite/NexilisLite/Source/View/Call/VideoConferenceViewController.swift

@@ -0,0 +1,1550 @@
+//
+//  VideoViewControllerQmera.swift
+//  Qmera
+//
+//  Created by Akhmad Al Qindi Irsyam on 07/10/21.
+//
+
+// Rn
+// (Kedip whiteboard)
+// extension Whiteboard
+// wbVC!.close wbTimer wbBlink
+
+import UIKit
+import nuSDKService
+import AVFoundation
+import NotificationBannerSwift
+
+class VideoConferenceViewController: UIViewController {
+    var dataPerson: [[String: String?]] = []
+    var fPin = ""
+    var wbRoomId = ""
+    var isInisiator = true
+    var isSpeaker = true
+    var isPresent = false
+    var callFCM = false
+    var listRemoteViewFix: [UIImageView] = [
+        UIImageView(),
+        UIImageView(),
+        UIImageView(),
+        UIImageView(),
+        UIImageView()
+    ]
+    var containerLabelName: [UIView] = [
+        UIView(),
+        UIView(),
+        UIView(),
+        UIView(),
+        UIView()
+    ]
+    let myImage = UIImageView()
+    let name = UILabel()
+    let profileImage = UIImageView()
+    let labelIncomingOutgoing = UILabel()
+    let buttonDecline = UIButton()
+    let buttonAccept = UIButton()
+    let zoomView = UIImageView()
+    let cameraView = UIImageView()
+    var constraintLeadingButtonDecline = NSLayoutConstraint()
+    var constraintBottomButtonDecline = NSLayoutConstraint()
+    var constraintBottomStackViewToolbar = NSLayoutConstraint()
+    var constraintLeftStackViewToolbar2 = NSLayoutConstraint()
+    let stackViewToolbar = UIStackView()
+    let stackViewToolbar2 = UIStackView()
+    var onScreenConstraintWB = [NSLayoutConstraint]()
+    let buttonWB = UIButton()
+    let buttonChat = UIButton()
+    var wbVC : WhiteboardViewController?
+    let buttonAddParticipant = UIButton()
+    let buttonSpeaker = UIButton()
+    let buttonRotate = UIButton()
+    var showStackViewToolbar = true
+    let scrollRemoteView = UIScrollView()
+    var isAutoAccept = false
+    var wbTimer = Timer()
+    var wbBlink = false
+    var showNotifCCEnd = false
+    var transformZoomAfterNewUserMore2 = false
+    var isAddCall = ""
+    var ticketId = ""
+    var roomId = ""
+    private var frontCamera = true
+    var users: [User] = []
+    let poweredByView: UIStackView = {
+        let stackView = UIStackView()
+        stackView.axis = .horizontal
+        stackView.spacing = 5
+        return stackView
+    }()
+    private var vcTimer = Timer()
+    private var containerTimerVC = UIView()
+    private var labelTimerVC = UILabel()
+    
+    let poweredByLabel: UILabel = {
+        let label = UILabel()
+        label.text = "Powered by Nexilis".localized()
+        return label
+    }()
+    
+    let qmeraLogo: UIButton = {
+        let image = UIImage(named: "Q-Button-PNG", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
+        let button = UIButton()
+        button.setImage(image, for: .normal)
+        button.imageView?.contentMode = .scaleAspectFit
+        button.imageEdgeInsets = UIEdgeInsets(top: 2, left: 2, bottom: 2, right: 2)
+        button.contentVerticalAlignment = .fill
+        button.contentHorizontalAlignment = .fill
+//        button.frame.size.width = 30
+//        button.frame.size.height = 30
+        return button
+    }()
+    
+    let nexilisLogo: UIButton = {
+        let image = UIImage(named: "pb_powered_button", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
+        let button = UIButton()
+        button.setImage(image, for: .normal)
+        button.imageView?.contentMode = .scaleAspectFit
+        button.imageEdgeInsets = UIEdgeInsets(top: 2, left: 2, bottom: 2, right: 2)
+        button.contentVerticalAlignment = .fill
+        button.contentHorizontalAlignment = .fill
+//        button.frame.size.width = 30
+//        button.frame.size.height = 30
+        return button
+    }()
+    
+    deinit {
+        navigationController?.navigationBar.setBackgroundImage(nil, for: .default)
+        navigationController?.navigationBar.shadowImage = nil
+        navigationController?.navigationBar.isTranslucent = false
+//        navigationController?.view.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
+        let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
+        navigationController?.navigationBar.titleTextAttributes = textAttributes
+        navigationController?.navigationBar.topItem?.backBarButtonItem = nil
+        navigationController?.interactivePopGestureRecognizer?.isEnabled = true
+        NotificationCenter.default.removeObserver(self)
+    }
+    
+    override func viewWillDisappear(_ animated: Bool) {
+        if self.isMovingFromParent {
+            navigationController?.navigationBar.setBackgroundImage(nil, for: .default)
+            navigationController?.navigationBar.shadowImage = nil
+            navigationController?.navigationBar.isTranslucent = false
+//            navigationController?.view.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
+            let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
+            navigationController?.navigationBar.titleTextAttributes = textAttributes
+            navigationController?.navigationBar.topItem?.backBarButtonItem = nil
+            navigationController?.interactivePopGestureRecognizer?.isEnabled = true
+            NotificationCenter.default.removeObserver(self)
+        }
+    }
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        Nexilis.setWhiteboardReceiver(receiver: self)
+        self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.black]
+        let navBarAppearance = UINavigationBarAppearance()
+        navBarAppearance.configureWithTransparentBackground()
+        navigationController?.navigationBar.standardAppearance = navBarAppearance
+        navigationController?.navigationBar.scrollEdgeAppearance = navBarAppearance
+        navigationController?.changeAppearance(clear: true)
+        
+        NotificationCenter.default.addObserver(self, selector: #selector(self.onStatusCall(_:)), name: NSNotification.Name(rawValue: Nexilis.listenerStatusCall), object: nil)
+//        NotificationCenter.default.addObserver(self, selector: #selector(onReceiveMessage(notification:)), name: NSNotification.Name(rawValue: Nexilis.listenerReceiveChat), object: nil)
+//        NotificationCenter.default.addObserver(self, selector: #selector(onCallFCM(notification:)), name: NSNotification.Name(rawValue: Nexilis.callFCM), object: nil)
+        
+        view.backgroundColor = .clear
+        navigationController?.navigationBar.topItem?.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
+        navigationController?.interactivePopGestureRecognizer?.isEnabled = false
+        navigationItem.setHidesBackButton(true, animated: false)
+        if fPin != ""{
+            getDataProfile(fPin: fPin)
+        }
+        
+        addZoomView()
+        addCameraView()
+        addListRemoteView()
+        addTimerVC()
+        didTapAcceptCallButton()
+        if isInisiator {
+            API.initiateCR(sConfRoom: roomId, nCamIdx: 1, nResIdx: 2, nVQuality: 4, ivRemoteView: listRemoteViewFix, ivLocalView: cameraView, ivRemoteZ: zoomView)
+            Nexilis.write(message: CoreMessage_TMessageBank.startVCallConference(blog_id: roomId, time: "\(Date().currentTimeMillis())"))
+        }
+        else {
+            API.joinCR(sConfRoom: roomId, nCamIdx: 1, nResIdx: 2, nVQuality: 4, ivRemoteView: listRemoteViewFix, ivLocalView: cameraView, ivRemoteZ: zoomView)
+            Nexilis.write(message: CoreMessage_TMessageBank.joinVCallConference(blog_id: roomId))
+        }
+        
+//        API.initiateCCall(sParty: dataPerson[0]["f_pin"]!, nCamIdx: 1, nResIdx: 2, nVQuality: 4, ivRemoteView: listRemoteViewFix, ivLocalView: cameraView, ivRemoteZ: zoomView)
+//        addBackgroundIncoming()
+//        addProfileNameCalling()
+//        Calling()
+//        addToolbar()
+//        addTimerVC()
+//        if isAutoAccept {
+//            didTapAcceptCallButton()
+//        }
+        
+    }
+    
+    func getDataProfile(fPin: String) {
+        let query = "SELECT f_pin, first_name, last_name, official_account, image_id, device_id, offline_mode, user_type FROM BUDDY where f_pin = '\(fPin)'"
+        Database.shared.database?.inTransaction({ (fmdb, rollback) in
+            do {
+                if let cursorData = Database.shared.getRecords(fmdb: fmdb, query: query) {
+                    var row: [String: String?] = [:]
+                    if cursorData.next() {
+                        row["f_pin"] = cursorData.string(forColumnIndex: 0)
+                        var name = ""
+                        if let firstname = cursorData.string(forColumnIndex: 1) {
+                            name = firstname
+                        }
+                        if let lastname = cursorData.string(forColumnIndex: 2) {
+                            name = name + " " + lastname
+                        }
+                        row["name"] = name
+                        row["picture"] = cursorData.string(forColumnIndex: 4)
+                        row["isOfficial"] = cursorData.string(forColumnIndex: 3)
+                        row["deviceId"] = cursorData.string(forColumnIndex: 5)
+                        row["isOffline"] = cursorData.string(forColumnIndex: 6)
+                        row["user_type"] = cursorData.string(forColumnIndex: 7)
+                        if fPin != User.getMyPin() {
+                            dataPerson.append(row)
+                        }
+                    } else {
+                        var row: [String: String?] = [:]
+                        row["f_pin"] = fPin
+                        row["name"] = "User".localized()
+                        row["picture"] = ""
+                        row["isOfficial"] = ""
+                        row["deviceId"] = ""
+                        row["isOffline"] = ""
+                        row["user_type"] = ""
+                        dataPerson.append(row)
+                    }
+                    cursorData.close()
+                } else {
+                    var row: [String: String?] = [:]
+                    row["f_pin"] = fPin
+                    row["name"] = "User".localized()
+                    row["picture"] = ""
+                    row["isOfficial"] = ""
+                    row["deviceId"] = ""
+                    row["isOffline"] = ""
+                    row["user_type"] = ""
+                    dataPerson.append(row)
+    //                if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getAddFriendQRCode(fpin: fPin)), response.isOk() {
+    //                    self.getDataProfile(fPin: fPin)
+    //                }
+    //                Nexilis.addFriend (fpin: "\(fPin)") { result in
+    //                    if result {
+    //                        self.getDataProfile(fPin: fPin)
+    //                    } else {
+    //                        let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
+    //                        imageView.tintColor = .white
+    //                        let banner = FloatingNotificationBanner(title: "Server busy, please try again later".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
+    //                        banner.show()
+    //                    }
+    //                }
+                }
+            } catch {
+                rollback.pointee = true
+                print("Access database error: \(error.localizedDescription)")
+            }
+        })
+    }
+    
+    func addTimerVC() {
+        view.addSubview(containerTimerVC)
+        containerTimerVC.anchor(top: view.topAnchor, paddingTop: 20, centerX: view.centerXAnchor, minWidth: 40)
+        containerTimerVC.makeRoundedView(radius: 8)
+        containerTimerVC.backgroundColor = .black.withAlphaComponent(0.3)
+        containerTimerVC.addSubview(labelTimerVC)
+        labelTimerVC.anchor(left: containerTimerVC.leftAnchor, right: containerTimerVC.rightAnchor, paddingLeft: 8, paddingRight: 8, centerX: containerTimerVC.centerXAnchor, centerY: containerTimerVC.centerYAnchor)
+        labelTimerVC.textColor = .white
+        containerTimerVC.isHidden = true
+    }
+    
+    func addZoomView() {
+        view.addSubview(zoomView)
+        zoomView.translatesAutoresizingMaskIntoConstraints = false
+        NSLayoutConstraint.activate([
+            zoomView.topAnchor.constraint(equalTo: view.topAnchor),
+            zoomView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+            zoomView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+            zoomView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+        ])
+        zoomView.backgroundColor = .secondaryColor
+        zoomView.isUserInteractionEnabled = true
+        zoomView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(hideToolbar)))
+    }
+    
+    func addCameraView() {
+        view.addSubview(cameraView)
+//        cameraView.frame = CGRect(x: view.frame.width - 130, y: 20, width: 120, height: 160)
+        cameraView.translatesAutoresizingMaskIntoConstraints = false
+        NSLayoutConstraint.activate([
+            cameraView.topAnchor.constraint(equalTo: view.topAnchor, constant: 20.0),
+            cameraView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10.0),
+            cameraView.widthAnchor.constraint(equalToConstant: 120.0),
+            cameraView.heightAnchor.constraint(equalToConstant: 160.0)
+        ])
+        cameraView.backgroundColor = .secondaryColor
+        cameraView.makeRoundedView(radius: 8)
+    }
+    
+    func addListRemoteView() {
+        view.addSubview(scrollRemoteView)
+        scrollRemoteView.translatesAutoresizingMaskIntoConstraints = false
+        NSLayoutConstraint.activate([
+            scrollRemoteView.topAnchor.constraint(equalTo: cameraView.bottomAnchor, constant: 10),
+            scrollRemoteView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10),
+            scrollRemoteView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
+            scrollRemoteView.widthAnchor.constraint(equalToConstant: 120.0)
+        ])
+        
+        scrollRemoteView.showsHorizontalScrollIndicator = false
+        scrollRemoteView.showsVerticalScrollIndicator = false
+        scrollRemoteView.contentSize.width = 120.0
+        scrollRemoteView.backgroundColor = .clear
+    }
+    
+    func addBackgroundIncoming() {
+        view.addSubview(myImage)
+        myImage.translatesAutoresizingMaskIntoConstraints = false
+        NSLayoutConstraint.activate([
+            myImage.topAnchor.constraint(equalTo: view.topAnchor),
+            myImage.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+            myImage.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+            myImage.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+        ])
+        myImage.backgroundColor = .lightGray
+        myImage.tintColor = .secondaryColor
+        let image = dataPerson[0]["picture"]!!
+        if image.isEmpty {
+            myImage.image = UIImage(systemName: "person")
+            myImage.contentMode = .scaleAspectFit
+        } else {
+            myImage.setImage(name: image)
+            myImage.contentMode = .scaleAspectFill
+        }
+//        let idMe = User.getMyPin() as String?
+//        Database().database?.inTransaction({ fmdb, rollback in
+//            if let c = Database().getRecords(fmdb: fmdb, query: "select image_id from BUDDY where f_pin = '\(idMe!)'"), c.next() {
+//                let image = c.string(forColumnIndex: 0)!
+//                if image.isEmpty {
+//                    myImage.image = UIImage(systemName: "person")
+//                    myImage.contentMode = .scaleAspectFit
+//                } else {
+//                    myImage.setImage(name: image)
+//                    myImage.contentMode = .scaleAspectFill
+//                }
+//                c.close()
+//            }
+//        })
+    }
+    
+    func addProfileNameCalling() {
+        view.addSubview(profileImage)
+        profileImage.translatesAutoresizingMaskIntoConstraints = false
+        profileImage.frame.size = CGSize(width: 60.0, height: 60.0)
+        NSLayoutConstraint.activate([
+            profileImage.topAnchor.constraint(equalTo: view.topAnchor, constant: 40.0),
+            profileImage.centerXAnchor.constraint(equalTo: view.centerXAnchor),
+            profileImage.widthAnchor.constraint(equalToConstant: 60.0),
+            profileImage.heightAnchor.constraint(equalToConstant: 63.0)
+        ])
+        profileImage.backgroundColor = .lightGray
+        profileImage.tintColor = .secondaryColor
+        profileImage.circle()
+        let image = dataPerson[0]["picture"]!!
+        if image.isEmpty {
+            profileImage.image = UIImage(systemName: "person")
+            profileImage.contentMode = .scaleAspectFit
+            profileImage.layer.borderWidth = 1
+            profileImage.layer.borderColor = UIColor.secondaryColor.cgColor
+        } else {
+            profileImage.setImage(name: image)
+            profileImage.contentMode = .scaleAspectFill
+        }
+        
+        view.addSubview(name)
+        name.translatesAutoresizingMaskIntoConstraints = false
+        NSLayoutConstraint.activate([
+            name.topAnchor.constraint(equalTo: profileImage.bottomAnchor, constant: 5.0),
+            name.centerXAnchor.constraint(equalTo: view.centerXAnchor)
+        ])
+        name.font = UIFont.systemFont(ofSize: 12)
+        name.backgroundColor = .black.withAlphaComponent(0.05)
+        name.layer.cornerRadius = 5.0
+        name.clipsToBounds = true
+        name.textColor = .mainColor
+        name.text = dataPerson[0]["name"]!?.trimmingCharacters(in: .whitespaces)
+    }
+    
+    func Calling() {
+        view.addSubview(labelIncomingOutgoing)
+        labelIncomingOutgoing.translatesAutoresizingMaskIntoConstraints = false
+        NSLayoutConstraint.activate([
+            labelIncomingOutgoing.topAnchor.constraint(equalTo: name.bottomAnchor, constant: 40.0),
+            labelIncomingOutgoing.centerXAnchor.constraint(equalTo: view.centerXAnchor)
+        ])
+        if isInisiator {
+            labelIncomingOutgoing.text = "Outgoing video call".localized() + "..."
+//            Nexilis.startAudio()
+            if ticketId.isEmpty {
+                if callFCM {
+                    DispatchQueue.global().async {
+                        if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.getCalling(fPin: self.dataPerson[0]["f_pin"]!!, type: "2"), timeout: 30 * 1000) {
+                            if response.isOk() {
+                                
+                            } else {
+                                DispatchQueue.main.async {
+                                    if self.labelIncomingOutgoing.isDescendant(of: self.view) {
+                                        self.labelIncomingOutgoing.text = "Busy".localized()
+                                    }
+                                    if self.buttonDecline.isDescendant(of: self.view) {
+                                        self.buttonDecline.removeFromSuperview()
+                                    }
+                                    if self.buttonAccept.isDescendant(of: self.view) {
+                                        self.buttonAccept.removeFromSuperview()
+                                    }
+                                }
+                            }
+                        } else {
+                            let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
+                            imageView.tintColor = .white
+                            let banner = FloatingNotificationBanner(title: "Unable to access servers. Try again later".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
+                            banner.show()
+                        }
+                    }
+                } else {
+                    Nexilis.ringbacktonePlayer?.play()
+                    API.initiateCCall(sParty: dataPerson[0]["f_pin"]!, nCamIdx: 1, nResIdx: 2, nVQuality: 4, ivRemoteView: listRemoteViewFix, ivLocalView: cameraView, ivRemoteZ: zoomView)
+                }
+            } else {
+                API.ccs(sTicketID: ticketId, nCamIdx: 1, nResIdx: 2, nVQuality: 4, ivRemoteView: listRemoteViewFix, ivLocalView: cameraView, ivRemoteZ: zoomView, bCameraOn: true)
+                if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.getIncomingCallCS(f_pin_opposite: users[0].pin), timeout: 30 * 1000){
+                    if response.mBodies[CoreMessage_TMessageKey.ERRCOD] != "01" {
+                        endAllCall()
+                    }
+                }
+            }
+        } else {
+            let systemSoundID: SystemSoundID = 1254
+            AudioServicesPlaySystemSound(systemSoundID)
+            labelIncomingOutgoing.text = "Incoming video call".localized() + "..."
+        }
+        labelIncomingOutgoing.font = UIFont.systemFont(ofSize: 12)
+        labelIncomingOutgoing.backgroundColor = .black.withAlphaComponent(0.05)
+        labelIncomingOutgoing.layer.cornerRadius = 5.0
+        labelIncomingOutgoing.clipsToBounds = true
+        labelIncomingOutgoing.textColor = .mainColor
+    }
+    
+    func addToolbar() {
+        view.addSubview(buttonDecline)
+        buttonDecline.translatesAutoresizingMaskIntoConstraints = false
+        buttonDecline.frame.size = CGSize(width: 70.0, height: 70.0)
+        if isInisiator {
+            constraintLeadingButtonDecline = buttonDecline.centerXAnchor.constraint(equalTo: view.centerXAnchor)
+        } else {
+            constraintLeadingButtonDecline = buttonDecline.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: view.frame.width * 0.2)
+        }
+        constraintBottomButtonDecline = buttonDecline.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -60.0)
+        NSLayoutConstraint.activate([
+            constraintBottomButtonDecline,
+            constraintLeadingButtonDecline,
+            buttonDecline.widthAnchor.constraint(equalToConstant: 70.0),
+            buttonDecline.heightAnchor.constraint(equalToConstant: 70.0)
+        ])
+        buttonDecline.backgroundColor = .red
+        buttonDecline.circle()
+        buttonDecline.setImage(UIImage(systemName: "xmark", withConfiguration: UIImage.SymbolConfiguration(pointSize: 30, weight: .medium, scale: .default)), for: .normal)
+        buttonDecline.tintColor = .white
+        buttonDecline.addTarget(self, action: #selector(didTapDeclineCallButton(sender:)), for: .touchUpInside)
+        
+        if !isInisiator{
+            view.addSubview(buttonAccept)
+            buttonAccept.translatesAutoresizingMaskIntoConstraints = false
+            buttonAccept.frame.size = CGSize(width: 70.0, height: 70.0)
+            NSLayoutConstraint.activate([
+                buttonAccept.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -60.0),
+                buttonAccept.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -(view.frame.width * 0.2)),
+                buttonAccept.widthAnchor.constraint(equalToConstant: 70.0),
+                buttonAccept.heightAnchor.constraint(equalToConstant: 70.0)
+            ])
+            buttonAccept.backgroundColor = .greenColor
+            buttonAccept.circle()
+            buttonAccept.setImage(UIImage(systemName: "checkmark", withConfiguration: UIImage.SymbolConfiguration(pointSize: 30, weight: .medium, scale: .default)), for: .normal)
+            buttonAccept.tintColor = .white
+            buttonAccept.addTarget(self, action: #selector(didTapAcceptCallButton), for: .touchUpInside)
+        }
+    }
+    
+    @objc func onCallFCM(notification: NSNotification) {
+        DispatchQueue.main.async { [self] in
+            let data:[AnyHashable : Any] = notification.userInfo!
+            if let l_pin = data["l_pin"] as? String {
+                Nexilis.ringtonePlayer?.play()
+                API.initiateCCall(sParty: l_pin, nCamIdx: 1, nResIdx: 2, nVQuality: 4, ivRemoteView: listRemoteViewFix, ivLocalView: cameraView, ivRemoteZ: zoomView)
+            }
+        }
+    }
+    
+    @objc func onReceiveMessage(notification: NSNotification) {
+        DispatchQueue.main.async {
+            let data:[AnyHashable : Any] = notification.userInfo!
+            if let dataMessage = data["message"] as? TMessage {
+                if (dataMessage.getCode() == CoreMessage_TMessageCode.PUSH_MEMBER_ROOM_CONTACT_CENTER) {
+                    let data = dataMessage.getBody(key: CoreMessage_TMessageKey.DATA)
+                    if !data.isEmpty {
+                        if let jsonArray = try! JSONSerialization.jsonObject(with: data.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [AnyObject] {
+                            var members = ""
+                            let idMe = User.getMyPin()!
+                            for json in jsonArray {
+                                if "\(json)" != idMe {
+                                    if members.isEmpty {
+                                        members = "\(json)"
+                                    } else {
+                                        members += ",\(json)"
+                                    }
+                                }
+                            }
+                            SecureUserDefaults.shared.set("\(members)", forKey: "membersCC")
+                        }
+                    }
+                }
+            }
+        }
+    }
+    
+    @objc func didTapDeclineCallButton(sender: AnyObject){
+        
+        let alert = LibAlertController(title: "End Video Call".localized(), message: "Are you sure you want to end video call?".localized(), preferredStyle: .alert)
+        alert.addAction(UIAlertAction(title: "No".localized(), style: UIAlertAction.Style.default, handler: nil))
+        alert.addAction(UIAlertAction(title: "Yes".localized(), style: UIAlertAction.Style.default, handler: {(_) in
+            if self.labelIncomingOutgoing.isDescendant(of: self.view) {
+                self.labelIncomingOutgoing.text = "Video call is over".localized()
+            }
+            if self.stackViewToolbar.isDescendant(of: self.view){
+                self.stackViewToolbar.removeFromSuperview()
+            }
+            if self.stackViewToolbar2.isDescendant(of: self.view){
+                self.stackViewToolbar2.removeFromSuperview()
+            }
+            if self.buttonWB.isDescendant(of: self.view){
+                self.buttonWB.removeFromSuperview()
+            }
+            if self.buttonChat.isDescendant(of: self.view){
+                self.buttonChat.removeFromSuperview()
+            }
+            if self.buttonDecline.isDescendant(of: self.view) {
+                self.buttonDecline.removeFromSuperview()
+            }
+            if self.buttonAccept.isDescendant(of: self.view) {
+                self.buttonAccept.removeFromSuperview()
+            }
+            if self.buttonRotate.isDescendant(of: self.view) {
+                self.buttonRotate.removeFromSuperview()
+            }
+            if self.wbVC != nil{
+                self.wbVC!.close?()
+            }
+            self.wbTimer.invalidate()
+            self.vcTimer.invalidate()
+            self.labelTimerVC.text = "Video call is over".localized()
+            self.endAllCall()
+            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
+                if self.isInisiator && !self.isPresent {
+                    self.navigationController?.popViewController(animated: true)
+                } else {
+                    self.dismiss(animated: true, completion: nil)
+                }
+            }
+        }))
+        self.present(alert, animated: true, completion: nil)
+    
+    }
+    
+    @objc func didTapAcceptCallButton() {
+        if !isInisiator{
+            let goAudioCall = Nexilis.checkMicPermission()
+            let goVideoCall = Nexilis.checkCameraPermission()
+            if goVideoCall == 0 {
+                let alert = LibAlertController(title: "Attention!".localized(), message: !goAudioCall && goVideoCall == 0 ? "Please allow microphone & camera permission in your settings".localized() : !goAudioCall ? "Please allow microphone permission in your settings".localized() : "Please allow camera permission in your settings", preferredStyle: .alert)
+                alert.addAction(UIAlertAction(title: "OK".localized(), style: UIAlertAction.Style.default, handler: {_ in
+                    if let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) {
+                        UIApplication.shared.open(url, options: [:], completionHandler: nil)
+                    }
+                }))
+                self.navigationController?.present(alert, animated: true, completion: nil)
+                return
+            } else if goVideoCall == -1 {
+                return
+            }
+//            Nexilis.startAudio()
+//            if ticketId.isEmpty {
+//                API.receiveCCall(sParty: dataPerson[0]["f_pin"]!, nCamIdx: 1, nResIdx: 2, nVQuality: 4, ivRemoteView: listRemoteViewFix, ivLocalView: cameraView,ivRemoteZ: zoomView)
+//            } else {
+//                API.csa(sTicketID: ticketId, nCamIdx: 1, nResIdx: 2, nVQuality: 4, ivRemoteView: listRemoteViewFix, ivLocalView: cameraView, ivRemoteZ: zoomView, bCameraOn: true)
+//            }
+//            Nexilis.ringtonePlayer?.stop()
+        }
+        DispatchQueue.main.async {
+//            self.myImage.removeFromSuperview()
+//            self.name.removeFromSuperview()
+//            self.profileImage.removeFromSuperview()
+//            self.labelIncomingOutgoing.removeFromSuperview()
+//            self.buttonAccept.removeFromSuperview()
+//            NSLayoutConstraint.deactivate([
+//                self.constraintLeadingButtonDecline,
+//                self.constraintBottomButtonDecline
+//            ])
+            self.view.addSubview(self.buttonDecline)
+            self.buttonDecline.translatesAutoresizingMaskIntoConstraints = false
+            self.buttonDecline.frame.size = CGSize(width: 70.0, height: 70.0)
+            if self.isInisiator {
+                self.constraintLeadingButtonDecline = self.buttonDecline.centerXAnchor.constraint(equalTo: self.view.centerXAnchor)
+            } else {
+                self.constraintLeadingButtonDecline = self.buttonDecline.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: self.view.frame.width * 0.2)
+            }
+            self.constraintBottomButtonDecline = self.buttonDecline.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -60.0)
+            NSLayoutConstraint.activate([
+                self.constraintBottomButtonDecline,
+                self.constraintLeadingButtonDecline,
+                self.buttonDecline.widthAnchor.constraint(equalToConstant: 70.0),
+                self.buttonDecline.heightAnchor.constraint(equalToConstant: 70.0)
+            ])
+            self.buttonDecline.backgroundColor = .red
+            self.buttonDecline.circle()
+            self.buttonDecline.setImage(UIImage(systemName: "xmark", withConfiguration: UIImage.SymbolConfiguration(pointSize: 30, weight: .medium, scale: .default)), for: .normal)
+            self.buttonDecline.tintColor = .white
+            self.buttonDecline.addTarget(self, action: #selector(self.didTapDeclineCallButton(sender:)), for: .touchUpInside)
+            self.addToolbarAfterAccept()
+            self.buttonDecline.setImage(UIImage(systemName: "phone.down", withConfiguration: UIImage.SymbolConfiguration(pointSize: 30, weight: .medium, scale: .default)), for: .normal)
+//            UIView.animate(withDuration: 1.0, animations: {
+//                self.view.layoutIfNeeded()
+//            })
+            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
+                self.containerTimerVC.isHidden = false
+                self.buttonRotate.isHidden = false
+                if self.isInisiator {
+                    self.buttonAddParticipant.isHidden = false
+                }
+                else {
+                    self.buttonAddParticipant.isHidden = true
+                }
+                self.buttonSpeaker.isHidden = false
+                self.buttonWB.isHidden = false
+                self.buttonChat.isHidden = false
+                self.poweredByView.isHidden = false
+                let connectDate = Date()
+                self.vcTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
+                    let format = Utils.callDurationFormatter.string(from: Date().timeIntervalSince(connectDate))
+                    self.labelTimerVC.text = format
+                }
+                self.vcTimer.fire()
+                API.adjustVolume(fValue: 10.0)
+            }
+        }
+    }
+    
+    @objc func didTapChatButton(){
+        let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
+        let members: String = SecureUserDefaults.shared.value(forKey: "membersCC") ?? ""
+        let officer = onGoingCC.isEmpty ? "" : onGoingCC.components(separatedBy: ",")[1]
+        let editorPersonalVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorPersonalVC") as! EditorPersonal
+        editorPersonalVC.hidesBottomBarWhenPushed = true
+        editorPersonalVC.unique_l_pin = officer
+        editorPersonalVC.fromNotification = true
+        editorPersonalVC.isContactCenter = true
+        editorPersonalVC.fPinContacCenter = members
+        editorPersonalVC.complaintId = ticketId
+        editorPersonalVC.onGoingCC = true
+        editorPersonalVC.isRequestContactCenter = false
+        editorPersonalVC.users = users
+        editorPersonalVC.fromVCAC = true
+        let navigationController = CustomNavigationController(rootViewController: editorPersonalVC)
+        navigationController.modalPresentationStyle = .overCurrentContext
+        navigationController.navigationBar.tintColor = .white
+//        navigationController.navigationBar.barTintColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
+        navigationController.navigationBar.isTranslucent = false
+        navigationController.navigationBar.overrideUserInterfaceStyle = .dark
+        navigationController.navigationBar.barStyle = .black
+        let cancelButtonAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
+        UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes, for: .normal)
+        let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
+        navigationController.navigationBar.titleTextAttributes = textAttributes
+        if UIApplication.shared.visibleViewController?.navigationController != nil {
+            UIApplication.shared.visibleViewController?.navigationController?.present(navigationController, animated: true, completion: nil)
+        } else {
+            UIApplication.shared.visibleViewController?.present(navigationController, animated: true, completion: nil)
+        }
+    }
+    
+    @objc func didTapWBButton(){
+        if(wbVC == nil){
+            wbVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "wbVC") as? WhiteboardViewController
+            if(wbRoomId.isEmpty){
+                let me = User.getMyPin()!
+                let tid = CoreMessage_TMessageUtil.getTID()
+                wbRoomId = "\(me)wbvc\(tid)"
+                wbVC!.roomId = wbRoomId
+                var destinations = [String]()
+                var destString = ""
+                for d in dataPerson{
+                    destinations.append(d["f_pin"]!!)
+                    if destString.isEmpty{
+                        destString = d["f_pin"]!!
+                    } else {
+                        destString = destString + ",\(d["f_pin"]!!)"
+                    }
+                }
+                wbVC!.destinations = destinations
+                wbVC!.sendInit()
+                SecureUserDefaults.shared.set("\(me),\(destString)", forKey: "wb_vc")
+            }
+            else {
+                self.wbTimer.invalidate()
+                self.buttonWB.backgroundColor = .lightGray
+                wbVC!.roomId = wbRoomId
+                wbVC!.sendJoin()
+            }
+        }
+        wbVC!.close = {
+            DispatchQueue.main.async {
+                if self.wbVC!.view.isDescendant(of: self.view){
+                    self.wbVC!.view.removeFromSuperview()
+                }
+                self.buttonDecline.isHidden = false
+                self.buttonSpeaker.isHidden = false
+                self.buttonAddParticipant.isHidden = false
+                self.buttonRotate.isHidden = false
+//                if(!self.wbRoomId.isEmpty){
+//                    DispatchQueue.main.async {
+//                        self.wbTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.runTimer), userInfo: nil, repeats: true)
+//                    }
+//                }
+            }
+        }
+        self.buttonDecline.isHidden = true
+        self.buttonSpeaker.isHidden = true
+        self.buttonAddParticipant.isHidden = true
+        self.buttonRotate.isHidden = true
+        addChild(wbVC!)
+        wbVC!.view.translatesAutoresizingMaskIntoConstraints = false
+        view.addSubview(wbVC!.view)
+        onScreenConstraintWB = [
+            wbVC!.view.topAnchor.constraint(equalTo: self.view.topAnchor),
+            wbVC!.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
+            wbVC!.view.rightAnchor.constraint(equalTo: self.view.rightAnchor),
+            wbVC!.view.leftAnchor.constraint(equalTo: self.view.leftAnchor),
+        ]
+           NSLayoutConstraint.activate(onScreenConstraintWB)
+             
+           // Notify the child view controller that the move is complete.
+           wbVC!.didMove(toParent: self)
+//        self.navigationController?.setNavigationBarHidden(false, animated: true)
+//        controller.modalPresentationStyle = .overCurrentContext
+//        self.navigationController?.present(controller, animated: true)
+    }
+    
+    func addToolbarAfterAccept() {
+        self.view.addSubview(self.stackViewToolbar)
+        self.stackViewToolbar.translatesAutoresizingMaskIntoConstraints = false
+        constraintBottomStackViewToolbar = self.stackViewToolbar.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -60.0)
+        NSLayoutConstraint.activate([
+            self.stackViewToolbar.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
+            constraintBottomStackViewToolbar
+        ])
+        self.stackViewToolbar.axis = .horizontal
+        self.stackViewToolbar.distribution = .equalSpacing
+        self.stackViewToolbar.alignment = .center
+        self.stackViewToolbar.spacing = 30
+        
+        self.view.addSubview(buttonRotate)
+        buttonRotate.translatesAutoresizingMaskIntoConstraints = false
+        buttonRotate.frame.size = CGSize(width: 70.0, height: 70.0)
+        NSLayoutConstraint.activate([
+            buttonRotate.widthAnchor.constraint(equalToConstant: 70.0),
+            buttonRotate.heightAnchor.constraint(equalToConstant: 70.0),
+            buttonRotate.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
+            buttonRotate.bottomAnchor.constraint(equalTo: self.stackViewToolbar.topAnchor, constant: -10.0)
+        ])
+        buttonRotate.backgroundColor = .secondaryColor
+        buttonRotate.setImage(UIImage(systemName: "arrow.triangle.2.circlepath.camera", withConfiguration: UIImage.SymbolConfiguration(pointSize: 30, weight: .medium, scale: .default)), for: .normal)
+        buttonRotate.tintColor = .mainColor
+        buttonRotate.circle()
+        buttonRotate.isHidden = true
+        buttonRotate.addTarget(self, action: #selector(camera(sender:)), for: .touchUpInside)
+        
+        if isInisiator {
+            view.addSubview(buttonAddParticipant)
+            buttonAddParticipant.translatesAutoresizingMaskIntoConstraints = false
+            buttonAddParticipant.frame.size = CGSize(width: 70.0, height: 70.0)
+            NSLayoutConstraint.activate([
+                buttonAddParticipant.widthAnchor.constraint(equalToConstant: 70.0),
+                buttonAddParticipant.heightAnchor.constraint(equalToConstant: 70.0)
+            ])
+            buttonAddParticipant.backgroundColor = .secondaryColor
+            buttonAddParticipant.setImage(UIImage(systemName: "person.badge.plus", withConfiguration: UIImage.SymbolConfiguration(pointSize: 30, weight: .medium, scale: .default)), for: .normal)
+            buttonAddParticipant.tintColor = .mainColor
+            buttonAddParticipant.circle()
+            buttonAddParticipant.isHidden = true
+            buttonAddParticipant.addTarget(self, action: #selector(didTapAddParticipantButton(sender:)), for: .touchUpInside)
+        }
+        
+        view.addSubview(buttonSpeaker)
+        buttonSpeaker.translatesAutoresizingMaskIntoConstraints = false
+        buttonSpeaker.frame.size = CGSize(width: 70.0, height: 70.0)
+        NSLayoutConstraint.activate([
+            buttonSpeaker.widthAnchor.constraint(equalToConstant: 70.0),
+            buttonSpeaker.heightAnchor.constraint(equalToConstant: 70.0)
+        ])
+        buttonSpeaker.setImage(UIImage(systemName: "speaker.wave.2", withConfiguration: UIImage.SymbolConfiguration(pointSize: 30, weight: .medium, scale: .default)), for: .normal)
+        self.buttonSpeaker.backgroundColor = .lightGray
+        self.buttonSpeaker.tintColor = .mainColor
+        buttonSpeaker.circle()
+        buttonSpeaker.isHidden = true
+        buttonSpeaker.addTarget(self, action: #selector(didTapSpeakerButton(sender:)), for: .touchUpInside)
+        
+        self.view.addSubview(self.stackViewToolbar2)
+        self.stackViewToolbar2.translatesAutoresizingMaskIntoConstraints = false
+        constraintLeftStackViewToolbar2 = self.stackViewToolbar2.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 10.0)
+        NSLayoutConstraint.activate([
+            self.stackViewToolbar2.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
+            constraintLeftStackViewToolbar2
+        ])
+        self.stackViewToolbar2.axis = .vertical
+        self.stackViewToolbar2.distribution = .equalSpacing
+        self.stackViewToolbar2.alignment = .center
+        self.stackViewToolbar2.spacing = 5
+        
+        view.addSubview(buttonWB)
+        buttonWB.translatesAutoresizingMaskIntoConstraints = false
+        buttonWB.frame.size = CGSize(width: 40.0, height: 40.0)
+        NSLayoutConstraint.activate([
+            buttonWB.widthAnchor.constraint(equalToConstant: 40.0),
+            buttonWB.heightAnchor.constraint(equalToConstant: 40.0)
+        ])
+        buttonWB.backgroundColor = .lightGray
+        buttonWB.setImage(UIImage(systemName: "ipad.landscape", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .medium, scale: .default)), for: .normal)
+        buttonWB.circle()
+        buttonWB.tintColor = .black
+        buttonWB.isHidden = true
+        buttonWB.addTarget(self, action: #selector(didTapWBButton), for: .touchUpInside)
+        
+        self.view.addSubview(poweredByView)
+        self.poweredByView.translatesAutoresizingMaskIntoConstraints = false
+        let constraintRightPowered =  self.poweredByView.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: -10.0)
+        let constraintBottomPowered = self.poweredByView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -10.0)
+        NSLayoutConstraint.activate([
+            constraintRightPowered,
+            constraintBottomPowered,
+            nexilisLogo.widthAnchor.constraint(equalToConstant: 30.0),
+            nexilisLogo.heightAnchor.constraint(equalToConstant: 30.0)
+        ])
+        
+        poweredByView.addArrangedSubview(poweredByLabel)
+        poweredByView.addArrangedSubview(nexilisLogo)
+        poweredByView.isHidden = true
+        
+        stackViewToolbar.addArrangedSubview(buttonAddParticipant)
+        stackViewToolbar.addArrangedSubview(buttonDecline)
+        stackViewToolbar.addArrangedSubview(buttonSpeaker)
+        stackViewToolbar2.addArrangedSubview(buttonWB)
+        stackViewToolbar2.addArrangedSubview(buttonChat)
+//        startFaceTimer()
+    }
+    
+    func endAllCall() {
+        if isInisiator {
+            Nexilis.ringbacktonePlayer?.stop()
+        } else {
+            Nexilis.ringtonePlayer?.stop()
+        }
+//        let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
+//        if !onGoingCC.isEmpty {
+//            let requester = onGoingCC.components(separatedBy: ",")[0]
+//            let officer = onGoingCC.isEmpty ? "" : onGoingCC.components(separatedBy: ",")[1]
+//            let complaintId = onGoingCC.isEmpty ? "" : onGoingCC.components(separatedBy: ",")[2]
+//            let idMe = User.getMyPin()!
+//            let startTimeCC: String = SecureUserDefaults.shared.value(forKey: "startTimeCC") ?? ""
+//            DispatchQueue.global().async {
+//                let date = "\(Date().currentTimeMillis())"
+//                Database.shared.database?.inTransaction({ (fmdb, rollback) in
+//                    do {
+//                        _ = try Database.shared.insertRecord(fmdb: fmdb, table: "CALL_CENTER_HISTORY", cvalues: [
+//                            "type" : "2",
+//                            "title" : "Contact Center".localized(),
+//                            "time" : startTimeCC,
+//                            "f_pin" : officer,
+//                            "data" : complaintId,
+//                            "time_end" : date,
+//                            "complaint_id" : complaintId,
+//                            "members" : "",
+//                            "requester": requester
+//                        ], replace: true)
+//                    } catch {
+//                        rollback.pointee = true
+//                        print("Access database error: \(error.localizedDescription)")
+//                    }
+//                })
+//                if officer == idMe {
+//                    _ = Nexilis.write(message: CoreMessage_TMessageBank.endCallCenter(complaint_id: complaintId, l_pin: requester))
+//                } else {
+//                    if requester == idMe {
+//                        _ = Nexilis.write(message: CoreMessage_TMessageBank.endCallCenter(complaint_id: complaintId, l_pin: officer))
+//                    } else {
+//                        _ = Nexilis.write(message: CoreMessage_TMessageBank.leaveCCRoomInvite(ticket_id: complaintId))
+//                    }
+//                }
+//                SecureUserDefaults.shared.removeValue(forKey: "onGoingCC")
+//                SecureUserDefaults.shared.removeValue(forKey: "membersCC")
+//                SecureUserDefaults.shared.removeValue(forKey: "startTimeCC")
+//                SecureUserDefaults.shared.removeValue(forKey: "waitingRequestCC")
+//            }
+//        }
+        _ = Nexilis.write(message: CoreMessage_TMessageBank.endVCallConference(blog_id: roomId))
+        cameraView.image = nil
+        zoomView.image = nil
+        listRemoteViewFix.removeAll()
+        dataPerson.removeAll()
+    }
+    
+    func setSpeaker(isSpeaker: Bool) {
+        DispatchQueue.main.async {
+            if (isSpeaker) {
+                self.buttonSpeaker.backgroundColor = .lightGray
+                self.buttonSpeaker.tintColor = .mainColor
+                self.buttonSpeaker.setImage(UIImage(systemName: "speaker.wave.2", withConfiguration: UIImage.SymbolConfiguration(pointSize: 30, weight: .medium, scale: .default)), for: .normal)
+            } else {
+                self.buttonSpeaker.backgroundColor = .secondaryColor
+                self.buttonSpeaker.tintColor = .mainColor
+                self.buttonSpeaker.setImage(UIImage(systemName: "speaker.slash", withConfiguration: UIImage.SymbolConfiguration(pointSize: 30, weight: .medium, scale: .default)), for: .normal)
+            }
+            self.isSpeaker = isSpeaker
+        }
+        Nexilis.setSpeaker(isSpeaker, isVideo: true)
+    }
+    
+    @objc func didTapSpeakerButton(sender: AnyObject){
+        setSpeaker(isSpeaker: !(self.isSpeaker))
+    }
+    
+    @objc func didTapAddParticipantButton(sender: AnyObject){
+        if let contactViewController = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "contactSID") as? ContactCallViewController {
+            contactViewController.isAddParticipantVideo = true
+            contactViewController.connectedCall = dataPerson
+            contactViewController.isDismiss = { data in
+                let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
+                if !onGoingCC.isEmpty {
+                    DispatchQueue.global().async {
+                        _ = Nexilis.write(message: CoreMessage_TMessageBank.getCCRoomInvite(l_pin: data["f_pin"]!!, ticket_id: onGoingCC.isEmpty ? "" : onGoingCC.components(separatedBy: ",")[2], channel: "2"))
+                    }
+                    DispatchQueue.main.async {
+                        self.isAddCall = data["f_pin"]!!
+                    }
+                } else {
+                    DispatchQueue.main.async {
+                        self.dataPerson.append(data)
+                        API.initiateCCall(sParty: data["f_pin"]!, nCamIdx: 1, nResIdx: 2, nVQuality: 4, ivRemoteView: self.listRemoteViewFix, ivLocalView: self.cameraView, ivRemoteZ: self.zoomView)
+                    }
+                }
+            }
+            present(CustomNavigationController(rootViewController: contactViewController), animated: true, completion: nil)
+        }
+    }
+    
+    @objc func camera(sender: Any?) {
+        if frontCamera {
+            API.changeCameraParam(nCameraIdx: 0, nResolutionIndex: 2, nQuality: 4)
+            frontCamera = false
+        } else {
+            API.changeCameraParam(nCameraIdx: 1, nResolutionIndex: 2, nQuality: 4)
+            frontCamera = true
+        }
+    }
+    
+    @objc func hideToolbar() {
+        DispatchQueue.main.async {
+            if self.showStackViewToolbar {
+                self.showStackViewToolbar = false
+                self.constraintBottomStackViewToolbar.constant = 150
+                self.constraintLeftStackViewToolbar2.constant = -60
+                UIView.animate(withDuration: 0.35, animations: {
+                    self.view.layoutIfNeeded()
+                })
+            } else {
+                self.showStackViewToolbar = true
+                self.constraintBottomStackViewToolbar.constant = -60
+                self.constraintLeftStackViewToolbar2.constant = 10
+                UIView.animate(withDuration: 0.35, animations: {
+                    self.view.layoutIfNeeded()
+                })
+            }
+        }
+    }
+    
+    @objc func onStatusCall(_ notification: NSNotification) {
+        let data = notification.userInfo
+        let state = (data?["state"] ?? 0) as! Int
+        let message = (data?["message"] ?? "") as! String
+        var remoteChannel = [String:String]()
+        let arrayMessage = message.split(separator: ",")
+        print(state)
+    }
+    
+    @objc func onStatusCall2(_ notification: NSNotification) {
+        let data = notification.userInfo
+        let state = (data?["state"] ?? 0) as! Int
+        let message = (data?["message"] ?? "") as! String
+        var remoteChannel = [String:String]()
+        let arrayMessage = message.split(separator: ",")
+        if(state == Nexilis.VIDEO_CALL_ZOOM){
+            DispatchQueue.main.async {
+                if self.dataPerson.count > 1 {
+                    if !self.transformZoomAfterNewUserMore2 {
+                        self.zoomView.transform   = CGAffineTransform.init(scaleX: 1.9, y: 1.9).rotated(by: (CGFloat.pi * 3)/2)
+                        self.transformZoomAfterNewUserMore2 = true
+                    }
+                }
+            }
+        }
+        else if (state == Nexilis.VIDEO_CAMERA_PARAMS_CHANED){
+            if(arrayMessage[3] == "0"){
+                DispatchQueue.main.async {
+                    if self.dataPerson.count == 1 && arrayMessage[2] == "1" && arrayMessage[4] == "1" {
+                        self.zoomView.transform = CGAffineTransform.init(scaleX: 1.9, y: 1.9).rotated(by: (CGFloat.pi * 3)/2)
+                    } else {
+                        self.zoomView.transform = CGAffineTransform.init(scaleX: 1.9, y: 1.9).rotated(by: (CGFloat.pi)/2)
+                    }
+                }
+            }
+        }
+        else if (state == Nexilis.VIDEO_CALL_OFFHOOK) {
+            if isInisiator {
+                Nexilis.ringbacktonePlayer?.stop()
+            }
+            let channel = arrayMessage[3]
+            remoteChannel[String(channel)] = String(arrayMessage[5])
+            DispatchQueue.main.async {
+                if (self.dataPerson.count == 1 && String(arrayMessage[1]) != self.dataPerson[0]["f_pin"]!!) {
+                    self.getDataProfile(fPin: String(arrayMessage[1]))
+                    for i in 0...1 {
+                        self.scrollRemoteView.addSubview(self.listRemoteViewFix[i])
+                        self.listRemoteViewFix[i].frame = CGRect(x: 0, y: 170 * i, width: 120, height: 160)
+                        self.listRemoteViewFix[i].backgroundColor = .clear
+                        self.listRemoteViewFix[i].makeRoundedView(radius: 8.0)
+                        self.scrollRemoteView.addSubview(self.containerLabelName[i])
+                        self.containerLabelName[i].frame = CGRect(x: 0, y: 170 * i, width: 120, height: 30)
+                        self.containerLabelName[i].backgroundColor = .orangeBNI.withAlphaComponent(0.5)
+                        self.containerLabelName[i].makeRoundedView(radius: 8.0)
+                        if i == 0 {
+                            if self.dataPerson[0]["user_type"] == "2" {
+                                self.listRemoteViewFix[0].transform = CGAffineTransform.init(scaleX: 1.4, y: 1.3).rotated(by: (CGFloat.pi)/2)
+                            } else {
+                                self.listRemoteViewFix[0].transform = CGAffineTransform.init(scaleX: 1.4, y: 1.3).rotated(by: (CGFloat.pi * 3 )/2)
+                            }
+                        } else {
+                            if arrayMessage[5] == "2" {
+                                self.listRemoteViewFix[1].transform = CGAffineTransform.init(scaleX: 1.4, y: 1.3).rotated(by: (CGFloat.pi)/2)
+                            } else {
+                                self.listRemoteViewFix[1].transform = CGAffineTransform.init(scaleX: 1.4, y: 1.3).rotated(by: (CGFloat.pi * 3 )/2)
+                            }
+                        }
+                        let pictureImage = self.dataPerson[i]["picture"] ?? ""
+                        let namePerson = self.dataPerson[i]["name"] ?? ""
+                        if (!pictureImage!.isEmpty) {
+                            self.listRemoteViewFix[i].setImage(name: pictureImage!)
+                            self.listRemoteViewFix[i].contentMode = .scaleAspectFill
+                        } else {
+                            self.listRemoteViewFix[i].image = UIImage(systemName: "person")
+                            self.listRemoteViewFix[i].backgroundColor = UIColor.systemGray6
+                            self.listRemoteViewFix[i].contentMode = .scaleAspectFit
+                        }
+                        let labelName = UILabel()
+                        self.containerLabelName[i].addSubview(labelName)
+                        labelName.anchor(left: self.containerLabelName[i].leftAnchor, right: self.containerLabelName[i].rightAnchor, paddingLeft: 5, paddingRight: 5, centerX: self.containerLabelName[i].centerXAnchor, centerY: self.containerLabelName[i].centerYAnchor)
+                        labelName.text = namePerson
+                        labelName.textAlignment = .center
+                        labelName.textColor = .white
+                    }
+                    self.scrollRemoteView.contentSize.height = CGFloat(170 * 2)
+                } else if self.dataPerson.count > 1 {
+                    if self.dataPerson.firstIndex(where: {$0["f_pin"]!! == arrayMessage[1]}) != nil {
+                        return
+                    }
+                    self.getDataProfile(fPin: String(arrayMessage[1]))
+                    let i = self.dataPerson.count - 1
+                    self.scrollRemoteView.addSubview(self.listRemoteViewFix[i])
+                    self.listRemoteViewFix[i].frame = CGRect(x: 0, y: 170 * i, width: 120, height: 160)
+                    self.listRemoteViewFix[i].backgroundColor = .clear
+                    self.listRemoteViewFix[i].makeRoundedView(radius: 8.0)
+                    self.scrollRemoteView.addSubview(self.containerLabelName[i])
+                    self.containerLabelName[i].frame = CGRect(x: 0, y: 170 * i, width: 120, height: 30)
+                    self.containerLabelName[i].backgroundColor = .orangeBNI.withAlphaComponent(0.5)
+                    self.containerLabelName[i].makeRoundedView(radius: 8.0)
+                    if arrayMessage[5] == "2" {
+                        self.listRemoteViewFix[i].transform = CGAffineTransform.init(scaleX: 1.4, y: 1.3).rotated(by: (CGFloat.pi)/2)
+                    } else {
+                        self.listRemoteViewFix[i].transform = CGAffineTransform.init(scaleX: 1.4, y: 1.3).rotated(by: (CGFloat.pi * 3 )/2)
+                    }
+                    let pictureImage = self.dataPerson[self.dataPerson.count - 1]["picture"] ?? ""
+                    let namePerson = self.dataPerson[self.dataPerson.count - 1]["name"] ?? ""
+                    if (!pictureImage!.isEmpty) {
+                        self.listRemoteViewFix[i].setImage(name: pictureImage!)
+                        self.listRemoteViewFix[i].contentMode = .scaleAspectFill
+                    } else {
+                        self.listRemoteViewFix[i].image = UIImage(systemName: "person")
+                        self.listRemoteViewFix[i].backgroundColor = UIColor.systemGray6
+                        self.listRemoteViewFix[i].contentMode = .scaleAspectFit
+                    }
+                    self.scrollRemoteView.contentSize.height = CGFloat(170 * (i + 1))
+                    let labelName = UILabel()
+                    self.containerLabelName[i].addSubview(labelName)
+                    labelName.anchor(left: self.containerLabelName[i].leftAnchor, right: self.containerLabelName[i].rightAnchor, paddingLeft: 5, paddingRight: 5, centerX: self.containerLabelName[i].centerXAnchor, centerY: self.containerLabelName[i].centerYAnchor)
+                    labelName.text = namePerson
+                    labelName.textAlignment = .center
+                    labelName.textColor = .white
+                }
+            }
+            if arrayMessage[5] == "2" && self.dataPerson.count == 1 {
+                DispatchQueue.main.async {
+                    self.zoomView.transform   = CGAffineTransform.init(scaleX: -1.9, y: 1.9).rotated(by: (CGFloat.pi)/2)
+                    self.zoomView.contentMode = .scaleAspectFit
+                }
+            }
+            else if self.dataPerson.count == 1 {
+                DispatchQueue.main.async {
+                    self.zoomView.transform   = CGAffineTransform.init(scaleX: 1.9, y: 1.9).rotated(by: (CGFloat.pi * 3)/2)
+                    self.zoomView.contentMode = .scaleAspectFit
+                }
+            } else if self.dataPerson.count > 1 {
+                DispatchQueue.main.async {
+                    for i in 0..<self.dataPerson.count {
+//                        self.listRemoteViewFix[i].image = self.listRemoteViewFix[i].image?.rotate(radians: (CGFloat.pi * 3 )/2)
+                        self.listRemoteViewFix[i].image = nil
+                        if self.dataPerson[i]["user_type"] == "2" || arrayMessage[5] == "2" {
+                            self.listRemoteViewFix[i].transform = CGAffineTransform.init(scaleX: 1.4, y: 1.3).rotated(by: (CGFloat.pi)/2)
+                        } else {
+                            self.listRemoteViewFix[i].transform = CGAffineTransform.init(scaleX: 1.4, y: 1.3).rotated(by: (CGFloat.pi * 3 )/2)
+                        }
+                    }
+                }
+            }
+            DispatchQueue.main.async {
+                if self.isInisiator && self.name.isDescendant(of: self.view) {
+                    self.didTapAcceptCallButton()
+                }
+                let indexPerson = self.dataPerson.firstIndex(where: {$0["f_pin"]!! == arrayMessage[1]})
+                if indexPerson != nil {
+                    self.dataPerson[indexPerson!]["user_type"] = String(arrayMessage[5])
+                }
+            }
+        } else if (state == Nexilis.VIDEO_CALL_END || state == Nexilis.AUDIO_CALL_END) {
+            if isInisiator {
+                Nexilis.ringbacktonePlayer?.stop()
+            } else {
+                Nexilis.ringtonePlayer?.stop()
+            }
+            let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
+            if !onGoingCC.isEmpty {
+                let requester = onGoingCC.components(separatedBy: ",")[0]
+                let officer = onGoingCC.isEmpty ? "" : onGoingCC.components(separatedBy: ",")[1]
+                if arrayMessage[0] == requester || arrayMessage[0] == officer {
+                    DispatchQueue.main.async {
+                        let controller = self.presentedViewController
+                        if controller != nil {
+                            controller!.dismiss(animated: true)
+                        }
+                        if !self.showNotifCCEnd{
+                            let imageView = UIImageView(image: UIImage(systemName: "info.circle"))
+                            imageView.tintColor = .white
+                            let banner = FloatingNotificationBanner(title: "Call Center Session has ended".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .info, colors: nil, iconPosition: .center)
+                            banner.show()
+                            self.showNotifCCEnd = true
+                        }
+                        if self.stackViewToolbar.isDescendant(of: self.view){
+                            self.stackViewToolbar.removeFromSuperview()
+                        }
+                        if self.stackViewToolbar2.isDescendant(of: self.view){
+                            self.stackViewToolbar2.removeFromSuperview()
+                        }
+                        if self.buttonWB.isDescendant(of: self.view){
+                            self.buttonWB.removeFromSuperview()
+                        }
+                        if self.buttonChat.isDescendant(of: self.view){
+                            self.buttonChat.removeFromSuperview()
+                        }
+                        if self.buttonDecline.isDescendant(of: self.view) {
+                            self.buttonDecline.removeFromSuperview()
+                        }
+                        if self.buttonAccept.isDescendant(of: self.view) {
+                            self.buttonAccept.removeFromSuperview()
+                        }
+                        if self.buttonRotate.isDescendant(of: self.view) {
+                            self.buttonRotate.removeFromSuperview()
+                        }
+                        if self.wbVC != nil{
+                            self.wbVC!.close?()
+                        }
+                        self.wbTimer.invalidate()
+                        _ = Nexilis.getWhiteboardDelegate()?.terminate()
+                    }
+                    DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
+                        self.endAllCall()
+                        self.dismiss(animated: true, completion: nil)
+                    }
+                    return
+                }
+            }
+            DispatchQueue.main.async {
+                if (self.dataPerson.count == 1) {
+                    if self.labelIncomingOutgoing.isDescendant(of: self.view) {
+                        self.labelIncomingOutgoing.text = "Video call is over".localized()
+                    }
+                    if self.stackViewToolbar.isDescendant(of: self.view){
+                        self.stackViewToolbar.removeFromSuperview()
+                    }
+                    if self.stackViewToolbar2.isDescendant(of: self.view){
+                        self.stackViewToolbar2.removeFromSuperview()
+                    }
+                    if self.buttonWB.isDescendant(of: self.view){
+                        self.buttonWB.removeFromSuperview()
+                    }
+                    if self.buttonChat.isDescendant(of: self.view){
+                        self.buttonChat.removeFromSuperview()
+                    }
+                    if self.buttonDecline.isDescendant(of: self.view) {
+                        self.buttonDecline.removeFromSuperview()
+                    }
+                    if self.buttonAccept.isDescendant(of: self.view) {
+                        self.buttonAccept.removeFromSuperview()
+                    }
+                    if self.buttonRotate.isDescendant(of: self.view) {
+                        self.buttonRotate.removeFromSuperview()
+                    }
+                    if self.wbVC != nil{
+                        self.wbVC!.close?()
+                    }
+                    self.wbTimer.invalidate()
+                    self.vcTimer.invalidate()
+                    self.labelTimerVC.text = "Video call is over".localized()
+                    _ = Nexilis.getWhiteboardDelegate()?.terminate()
+                    let controller = self.presentedViewController
+                    if controller != nil {
+                        controller!.dismiss(animated: true)
+                    }
+                    DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
+                        self.endAllCall()
+                        if self.isInisiator && !self.isPresent {
+                            self.navigationController?.popViewController(animated: true)
+                        } else {
+                            self.dismiss(animated: true, completion: nil)
+                        }
+                    }
+                } else {
+                    let indexPerson = self.dataPerson.firstIndex(where: {$0["f_pin"]!! == arrayMessage[0]})
+                    if indexPerson != nil {
+                        if (self.dataPerson.count == 2) {
+                            self.containerLabelName.forEach({ $0.subviews.forEach({ $0.removeFromSuperview() }) })
+                            self.scrollRemoteView.subviews.forEach({ $0.removeFromSuperview() })
+                        } else {
+                            self.containerLabelName[indexPerson! + indexPerson!].subviews.forEach({ $0.removeFromSuperview() })
+                            self.scrollRemoteView.subviews[indexPerson! + indexPerson!].removeFromSuperview()
+                            self.containerLabelName[indexPerson! + indexPerson!].subviews.forEach({ $0.removeFromSuperview() })
+                            self.scrollRemoteView.subviews[indexPerson! + indexPerson!].removeFromSuperview()
+                            if indexPerson! + 1 <= self.listRemoteViewFix.count {
+                                let iLoop = (self.listRemoteViewFix.count - 1) - (indexPerson! + 1)
+                                if iLoop >= 0 {
+                                    for i in 0...iLoop {
+                                        let viewAfterRemote = self.listRemoteViewFix[(indexPerson! + i) + 1]
+                                        let viewAfterName = self.containerLabelName[(indexPerson! + i) + 1]
+                                        viewAfterRemote.frame.origin.y = viewAfterRemote.frame.origin.y - 170
+                                        viewAfterName.frame.origin.y = viewAfterName.frame.origin.y - 170
+                                        UIView.animate(withDuration: 0.35, animations: {
+                                            self.scrollRemoteView.layoutIfNeeded()
+                                        })
+                                    }
+                                }
+                            }
+                        }
+                        self.dataPerson.remove(at: indexPerson!)
+                    }
+                    if !onGoingCC.isEmpty {
+                        if let pin = arrayMessage.first, let index = self.users.firstIndex(of: User(pin: String(pin))) {
+                            self.users.remove(at: index)
+                        }
+                    }
+                    
+                    if self.dataPerson.count == 1 {
+                        self.transformZoomAfterNewUserMore2 = false
+                        self.zoomView.transform   = CGAffineTransform.init(scaleX: 1.9, y: 1.9).rotated(by: (CGFloat.pi)/2)
+                    }
+                }
+            }
+        } else if (state == Nexilis.OFFLINE) {
+            if isInisiator {
+                Nexilis.ringbacktonePlayer?.stop()
+            } else {
+                Nexilis.ringtonePlayer?.stop()
+            }
+            let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
+            DispatchQueue.main.async {
+                if (self.dataPerson.count == 1) {
+                    if self.labelIncomingOutgoing.isDescendant(of: self.view) {
+                        self.labelIncomingOutgoing.text = "Offline".localized()
+                    }
+                    if self.buttonDecline.isDescendant(of: self.view) {
+                        self.buttonDecline.removeFromSuperview()
+                    }
+                    if self.buttonAccept.isDescendant(of: self.view) {
+                        self.buttonAccept.removeFromSuperview()
+                    }
+                } else {
+                    let indexPerson = self.dataPerson.firstIndex(where: {$0["f_pin"]!! == arrayMessage[0]})
+                    if indexPerson != nil {
+                        if (self.dataPerson.count == 2) {
+                            self.containerLabelName.forEach({ $0.subviews.forEach({ $0.removeFromSuperview() }) })
+                            self.scrollRemoteView.subviews.forEach({ $0.removeFromSuperview() })
+                        } else {
+                            self.containerLabelName[indexPerson! + indexPerson!].subviews.forEach({ $0.removeFromSuperview() })
+                            self.scrollRemoteView.subviews[indexPerson! + indexPerson!].removeFromSuperview()
+                            self.containerLabelName[indexPerson! + indexPerson!].subviews.forEach({ $0.removeFromSuperview() })
+                            self.scrollRemoteView.subviews[indexPerson! + indexPerson!].removeFromSuperview()
+                            if indexPerson! + 1 <= self.listRemoteViewFix.count {
+                                let viewAfterRemote = self.listRemoteViewFix[indexPerson! + 1]
+                                let viewAfterName = self.containerLabelName[indexPerson! + 1]
+                                viewAfterRemote.frame.origin.y = viewAfterRemote.frame.origin.y - 170
+                                viewAfterName.frame.origin.y = viewAfterName.frame.origin.y - 170
+                                UIView.animate(withDuration: 0.35, animations: {
+                                    self.scrollRemoteView.layoutIfNeeded()
+                                })
+                            }
+                        }
+                    }
+                    if !onGoingCC.isEmpty {
+                        if let pin = arrayMessage.first, let index = self.users.firstIndex(of: User(pin: String(pin))) {
+                            self.users.remove(at: index)
+                            if !onGoingCC.isEmpty && self.users.count != 0 {
+                                DispatchQueue.main.async {
+                                    var members = ""
+                                    for user in self.users {
+                                        if members.isEmpty {
+                                            members = "\(user.pin)"
+                                        } else {
+                                            members = ",\(user.pin)"
+                                        }
+                                    }
+                                    SecureUserDefaults.shared.set("\(members)", forKey: "membersCC")
+                                }
+                            }
+                        }
+                    }
+                    self.dataPerson.remove(at: indexPerson!)
+                }
+            }
+            if (self.dataPerson.count == 1) {
+                DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
+                    self.endAllCall()
+                    if self.isInisiator && onGoingCC.isEmpty && !self.isPresent {
+                        self.navigationController?.popViewController(animated: true)
+                    } else {
+                        self.dismiss(animated: true, completion: nil)
+                    }
+                }
+            }
+        } else if (state == Nexilis.BUSY) {
+            if isInisiator {
+                Nexilis.ringbacktonePlayer?.stop()
+            } else {
+                Nexilis.ringtonePlayer?.stop()
+            }
+            let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
+            DispatchQueue.main.async { [self] in
+                if (self.dataPerson.count == 1) {
+                    if self.labelIncomingOutgoing.isDescendant(of: self.view) {
+                        self.labelIncomingOutgoing.text = "Busy".localized()
+                    }
+                    if self.buttonDecline.isDescendant(of: self.view) {
+                        self.buttonDecline.removeFromSuperview()
+                    }
+                    if self.buttonAccept.isDescendant(of: self.view) {
+                        self.buttonAccept.removeFromSuperview()
+                    }
+                } else {
+                    let indexPerson = self.dataPerson.firstIndex(where: {$0["f_pin"]!! == arrayMessage[0]})
+                    if indexPerson != nil {
+                        if (self.dataPerson.count == 2) {
+                            self.containerLabelName.forEach({ $0.subviews.forEach({ $0.removeFromSuperview() }) })
+                            self.scrollRemoteView.subviews.forEach({ $0.removeFromSuperview() })
+                        } else {
+                            self.containerLabelName[indexPerson! + indexPerson!].subviews.forEach({ $0.removeFromSuperview() })
+                            self.scrollRemoteView.subviews[indexPerson! + indexPerson!].removeFromSuperview()
+                            self.containerLabelName[indexPerson! + indexPerson!].subviews.forEach({ $0.removeFromSuperview() })
+                            self.scrollRemoteView.subviews[indexPerson! + indexPerson!].removeFromSuperview()
+                            if indexPerson! + 1 <= self.listRemoteViewFix.count {
+                                let viewAfterRemote = self.listRemoteViewFix[indexPerson! + 1]
+                                let viewAfterName = self.containerLabelName[indexPerson! + 1]
+                                viewAfterRemote.frame.origin.y = viewAfterRemote.frame.origin.y - 170
+                                viewAfterName.frame.origin.y = viewAfterName.frame.origin.y - 170
+                                UIView.animate(withDuration: 0.35, animations: {
+                                    self.scrollRemoteView.layoutIfNeeded()
+                                })
+                            }
+                        }
+                    }
+                    if !onGoingCC.isEmpty {
+                        if let pin = arrayMessage.first, let index = self.users.firstIndex(of: User(pin: String(pin))) {
+                            self.users.remove(at: index)
+                            if !onGoingCC.isEmpty && users.count != 0 {
+                                DispatchQueue.main.async {
+                                    var members = ""
+                                    for user in self.users {
+                                        if members.isEmpty {
+                                            members = "\(user.pin)"
+                                        } else {
+                                            members = ",\(user.pin)"
+                                        }
+                                    }
+                                    SecureUserDefaults.shared.set("\(members)", forKey: "membersCC")
+                                }
+                            }
+                        }
+                    }
+                    self.dataPerson.remove(at: indexPerson!)
+                }
+            }
+            if (self.dataPerson.count == 1) {
+                DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
+                    self.endAllCall()
+                    if self.isInisiator && onGoingCC.isEmpty && !self.isPresent {
+                        self.navigationController?.popViewController(animated: true)
+                    } else {
+                        self.dismiss(animated: true, completion: nil)
+                    }
+                }
+            }
+        } else if (state == Nexilis.VIDEO_CALL_RINGING) {
+            DispatchQueue.main.async {
+                if (self.dataPerson.count > 1) {
+                    if (self.dataPerson.count == 2) {
+                        for i in 0...1{
+                            self.scrollRemoteView.addSubview(self.listRemoteViewFix[i])
+                            self.listRemoteViewFix[i].frame = CGRect(x: 0, y: 170 * i, width: 120, height: 160)
+                            self.listRemoteViewFix[i].backgroundColor = .clear
+                            self.listRemoteViewFix[i].makeRoundedView(radius: 8.0)
+                            self.scrollRemoteView.addSubview(self.containerLabelName[i])
+                            self.containerLabelName[i].frame = CGRect(x: 0, y: 170 * i, width: 120, height: 30)
+                            self.containerLabelName[i].backgroundColor = .orangeBNI.withAlphaComponent(0.5)
+                            self.containerLabelName[i].makeRoundedView(radius: 8.0)
+                            let pictureImage = self.dataPerson[i]["picture"] ?? ""
+                            let namePerson = self.dataPerson[i]["name"] ?? ""
+                            if (!pictureImage!.isEmpty) {
+                                self.listRemoteViewFix[i].setImage(name: pictureImage!)
+                                self.listRemoteViewFix[i].contentMode = .scaleAspectFill
+                            } else {
+                                self.listRemoteViewFix[i].image = UIImage(systemName: "person")
+                                self.listRemoteViewFix[i].backgroundColor = UIColor.systemGray6
+                                self.listRemoteViewFix[i].contentMode = .scaleAspectFit
+                            }
+                            let labelName = UILabel()
+                            self.containerLabelName[i].addSubview(labelName)
+                            labelName.anchor(left: self.containerLabelName[i].leftAnchor, right: self.containerLabelName[i].rightAnchor, paddingLeft: 5, paddingRight: 5, centerX: self.containerLabelName[i].centerXAnchor, centerY: self.containerLabelName[i].centerYAnchor)
+                            labelName.text = namePerson
+                            labelName.textAlignment = .center
+                            labelName.textColor = .white
+                        }
+                        self.scrollRemoteView.contentSize.height = CGFloat(170 * 2)
+                    } else {
+                        let i = self.dataPerson.count - 1
+                        self.scrollRemoteView.addSubview(self.listRemoteViewFix[i])
+                        self.listRemoteViewFix[i].frame = CGRect(x: 0, y: 170 * i, width: 120, height: 160)
+                        self.listRemoteViewFix[i].backgroundColor = .clear
+                        self.listRemoteViewFix[i].makeRoundedView(radius: 8.0)
+                        self.scrollRemoteView.addSubview(self.containerLabelName[i])
+                        self.containerLabelName[i].frame = CGRect(x: 0, y: 170 * i, width: 120, height: 30)
+                        self.containerLabelName[i].backgroundColor = .orangeBNI.withAlphaComponent(0.5)
+                        self.containerLabelName[i].makeRoundedView(radius: 8.0)
+                        let pictureImage = self.dataPerson[self.dataPerson.count - 1]["picture"] ?? ""
+                        let namePerson = self.dataPerson[self.dataPerson.count - 1]["name"] ?? ""
+                        if (!pictureImage!.isEmpty) {
+                            self.listRemoteViewFix[i].setImage(name: pictureImage!)
+                            self.listRemoteViewFix[i].contentMode = .scaleAspectFill
+                        } else {
+                            self.listRemoteViewFix[i].image = UIImage(systemName: "person")
+                            self.listRemoteViewFix[i].backgroundColor = UIColor.systemGray6
+                            self.listRemoteViewFix[i].contentMode = .scaleAspectFit
+                        }
+                        self.scrollRemoteView.contentSize.height = CGFloat(170 * (i + 1))
+                        let labelName = UILabel()
+                        self.containerLabelName[i].addSubview(labelName)
+                        labelName.anchor(left: self.containerLabelName[i].leftAnchor, right: self.containerLabelName[i].rightAnchor, paddingLeft: 5, paddingRight: 5, centerX: self.containerLabelName[i].centerXAnchor, centerY: self.containerLabelName[i].centerYAnchor)
+                        labelName.text = namePerson
+                        labelName.textAlignment = .center
+                        labelName.textColor = .white
+                    }
+                }
+            }
+        }
+    }
+}
+
+extension VideoConferenceViewController : WhiteboardReceiver {
+    
+    func incomingWB(roomId: String) {
+        //print("incoming wb")
+        self.wbTimer.invalidate()
+        if(wbRoomId.isEmpty){
+            //print("wbroom empty")
+            DispatchQueue.main.async {
+                self.wbTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.runTimer), userInfo: nil, repeats: true)
+            }
+            let me = User.getMyPin()!
+            var destString = ""
+            for d in dataPerson{
+                if d["f_pin"]!! == roomId.components(separatedBy: "wbvc")[0] {
+                    continue
+                }
+                if destString.isEmpty{
+                    destString = d["f_pin"]!!
+                } else {
+                    destString = destString + ",\(d["f_pin"]!!)"
+                }
+            }
+            if destString.isEmpty {
+                SecureUserDefaults.shared.set("\(roomId.components(separatedBy: "wbvc")[0]),\(me)", forKey: "wb_vc")
+            } else {
+                SecureUserDefaults.shared.set("\(roomId.components(separatedBy: "wbvc")[0]),\(me),\(destString)", forKey: "wb_vc")
+            }
+            wbRoomId = roomId
+        }
+    }
+    
+    func cancel(roomId: String) {
+        DispatchQueue.main.async {
+            self.wbTimer.invalidate()
+            self.wbBlink = false
+            self.buttonWB.backgroundColor = .lightGray
+            self.buttonWB.setNeedsDisplay()
+        }
+        wbRoomId = ""
+    }
+    
+    @objc func runTimer(){
+        DispatchQueue.main.async {
+            self.wbBlink = !self.wbBlink
+            if(self.wbBlink){
+                //print("set wb blink on")
+                self.buttonWB.backgroundColor = .green
+            }
+            else {
+                //print("set wb blink off")
+                self.buttonWB.backgroundColor = .lightGray
+            }
+            self.buttonWB.setNeedsDisplay()
+        }
+    }
+    
+}

+ 49 - 1
NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift

@@ -5156,6 +5156,34 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
                 streamingNav.view.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
                 streamingNav.navigationBar.isTranslucent = false
                 navigationController?.present(streamingNav, animated: true, completion: nil)
+            } else if attachmentFlag == "25" {
+                let conferenceController = CreateSeminarViewController()
+                if let messageText = message["message_text"],
+                   let messageText = messageText as? String,
+                   var json = try! JSONSerialization.jsonObject(with: messageText.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
+                    if json["blog"] == nil {
+                        json["blog"] = message["blog"] ?? nil
+                    }
+                    json["participant"] = message["members"]
+                    let start = json["time"] as? Int64 ?? 0
+                    json["start"] = String(Date(milliseconds: start).format(dateFormat: "dd/MM/yyyy HH:mm"))
+                    conferenceController.data = json
+                    conferenceController.isJoin = true
+                }
+                let conferenceNav = CustomNavigationController(rootViewController: conferenceController)
+                conferenceNav.modalPresentationStyle = .custom
+                conferenceNav.navigationBar.tintColor = .white
+                conferenceNav.navigationBar.barTintColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
+                conferenceNav.navigationBar.isTranslucent = false
+                conferenceNav.navigationBar.overrideUserInterfaceStyle = .dark
+                conferenceNav.navigationBar.barStyle = .black
+                let cancelButtonAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
+                UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes, for: .normal)
+                let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
+                conferenceNav.navigationBar.titleTextAttributes = textAttributes
+                conferenceNav.view.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
+                conferenceNav.navigationBar.isTranslucent = false
+                navigationController?.present(conferenceNav, animated: true, completion: nil)
             } else if  message["message_scope_id"] as? String == "18" {
                 let formView = FormEditor()
                 let messageText =  message["message_text"]  as? String ?? ""
@@ -5738,7 +5766,8 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
         let topMarginText = messageText.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 15)
         messageText.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
         messageText.font = .systemFont(ofSize: 12)
-        if dataMessages[indexPath.row]["attachment_flag"] as? String == "27" || dataMessages[indexPath.row]["attachment_flag"] as? String == "26" || dataMessages[indexPath.row]["message_scope_id"] as? String == "18" {
+        if dataMessages[indexPath.row]["attachment_flag"] as? String == "27" || dataMessages[indexPath.row]["attachment_flag"] as? String == "26" ||
+            dataMessages[indexPath.row]["attachment_flag"] as? String == "25" || dataMessages[indexPath.row]["message_scope_id"] as? String == "18" {
             messageText.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 85).isActive = true
             let imageLS = UIImageView()
             containerMessage.addSubview(imageLS)
@@ -5753,6 +5782,8 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
                 imageLS.image = UIImage(named: "pb_seminar_wpr", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
             } else if dataMessages[indexPath.row]["attachment_flag"]  as? String ?? "" == "27" {
                 imageLS.image = UIImage(named: "pb_live_tv", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
+            } else if dataMessages[indexPath.row]["attachment_flag"] as! String == "25" {
+                imageLS.image = UIImage(named: "pb_vroom", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
             } else if dataMessages[indexPath.row]["message_scope_id"] as? String == "18" {
                 imageLS.image = UIImage(systemName: "doc.richtext.fill")
                 imageLS.tintColor = .mainColor
@@ -5822,6 +5853,23 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
                     messageText.isUserInteractionEnabled = false
                 }
             }
+            else if attachmentFlag == "25" {
+                let data = textChat
+                if let json = try! JSONSerialization.jsonObject(with: data.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
+                    let title = json["title"] as? String ?? ""
+                    let blog = json["blog"] as? String ?? ""
+                    let by = json["by"] as? String ?? ""
+                    let start = json["time"] as? Int64 ?? 0
+                    let textVCR = "Video Conference Room".localized()
+                    var type = "*\(textVCR)*"
+                    if let c = User.getData(pin: by) {
+                        let name = c.fullName
+                        stringLS = "\(type) \nTitle: \(title) \nStart: \(Date(milliseconds: start).format(dateFormat: "dd/MM/yyyy HH:mm")) \nInitiator: \(name) \n\n*^Room ID: ^*\n*^\(blog)^*"
+                    }
+                    messageText.attributedText = stringLS.richText()
+                    messageText.isUserInteractionEnabled = false
+                }
+            }
             else if attachmentFlag == "11" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"]  as? String ?? "" != "1") && (dataMessages[indexPath.row]["lock"] as? String != "2") {
                 messageText.text = ""
                 topMarginText.constant = topMarginText.constant + 100

+ 20 - 1
NexilisLite/NexilisLite/Source/View/Chat/MessageInfo.swift

@@ -628,7 +628,7 @@ class MessageInfo: UIViewController, UITableViewDelegate, UITableViewDataSource,
             let topMarginText = messageText.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 15)
             topMarginText.isActive = true
             messageText.textColor = .black
-            if data["attachment_flag"] as? String == "27" || data["attachment_flag"] as? String == "26" || data["message_scope_id"] as? String == "18" {
+            if data["attachment_flag"] as? String == "27" || data["attachment_flag"] as? String == "26" || data["attachment_flag"] as? String == "25" || data["message_scope_id"] as? String == "18" {
                 messageText.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 85).isActive = true
                 let imageLS = UIImageView()
                 containerMessage.addSubview(imageLS)
@@ -699,6 +699,25 @@ class MessageInfo: UIViewController, UITableViewDelegate, UITableViewDataSource,
                         })
                     }
                 }
+                else if attachmentFlag == "25" {
+                    let data = textChat
+                    if let json = try! JSONSerialization.jsonObject(with: data.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
+                        Database().database?.inTransaction({ fmdb, rollback in
+                            var stringLS = ""
+                            let title = json["title"] as? String ?? ""
+                            let blog = json["blog"] as? String ?? ""
+                            let by = json["by"] as? String ?? ""
+                            let start = json["time"] as? Int64 ?? 0
+                            let textVCR = "Video Conference Room".localized()
+                            var type = "*\(textVCR)*"
+                            if let c = Database().getRecords(fmdb: fmdb, query: "select first_name || ' ' || last_name from BUDDY where f_pin = '\(by)'"), c.next() {
+                                let name = c.string(forColumnIndex: 0)!
+                                stringLS = "\(type) \nTitle: \(title) \nStart: \(Date(milliseconds: start).format(dateFormat: "dd/MM/yyyy HH:mm")) \nInitiator: \(name) \n\n*^Room ID: ^*\n*^\(blog)^*"
+                            }
+                            messageText.attributedText = stringLS.richText()
+                        })
+                    }
+                }
                 else if attachmentFlag == "11" {
                     messageText.text = ""
                     topMarginText.constant = topMarginText.constant + 100

+ 216 - 262
NexilisLite/NexilisLite/Source/View/Streaming/CreateSeminarViewController.swift

@@ -12,26 +12,20 @@ import AVFoundation
 public class CreateSeminarViewController: UITableViewController {
     
     var isJoin = false
+    var startTime = 0
     
     var data: [String: Any] = [:]
     
     private enum Section {
-        case chooser
         case title
-        case description
-        case users
-        case groups
+        case start
+        case participants
     }
     
     private var sections: [Section] = [
-        .chooser,
         .title,
-        .description
-    ]
-    
-    private var chooser: [Chooser] = [
-        Chooser(title: "Target Audience".localized(), value: AudienceViewController().data.first),
-        Chooser(title: "Promotion Type".localized(), value: TypeViewController().data.first)
+        .start,
+        .participants
     ]
     
     private var users: [User] = [] {
@@ -42,14 +36,6 @@ public class CreateSeminarViewController: UITableViewController {
         }
     }
     
-    private var groups: [Group] = [] {
-        didSet {
-            DispatchQueue.main.async { [weak self] in
-                self?.tableView.reloadData()
-            }
-        }
-    }
-    
     private let cellIdentifier = "reuseIdentifier"
     
     lazy var table: UITableView = {
@@ -65,11 +51,18 @@ public class CreateSeminarViewController: UITableViewController {
         return textField
     }()
     
-    lazy var descriptionView: UITextView = {
-        let textView = UITextView()
-        textView.text = "Description".localized()
-        textView.textColor = UIColor.lightGray
-        return textView
+    lazy var startView: UITextField = {
+        let textField = UITextField()
+        textField.placeholder = "Start".localized()
+        textField.borderStyle = .none
+        return textField
+    }()
+    
+    let datePicker: UIDatePicker = {
+        let picker = UIDatePicker()
+        picker.datePickerMode = .dateAndTime // Allows both date and time selection
+        picker.preferredDatePickerStyle = .wheels // Optional: change the picker style
+        return picker
     }()
     
     deinit {
@@ -80,13 +73,19 @@ public class CreateSeminarViewController: UITableViewController {
     public override func viewDidLoad() {
         super.viewDidLoad()
         
-        title = "Seminar".localized()
+        title = "Video Conference Room".localized()
+        
+        let attributes = [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 16.0), NSAttributedString.Key.foregroundColor: UIColor.white]
+        let navBarAppearance = UINavigationBarAppearance()
+        navBarAppearance.configureWithOpaqueBackground()
+        navBarAppearance.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : UIColor.mainColor
+        navBarAppearance.titleTextAttributes = attributes
+        navigationController?.navigationBar.standardAppearance = navBarAppearance
+        navigationController?.navigationBar.scrollEdgeAppearance = navBarAppearance
         
         navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(didTapCancel(sender:)))
         navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Start".localized(), style: .plain, target: self, action: #selector(didTapRight(sender:)))
         
-        descriptionView.delegate = self
-        
         let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
         tapGesture.cancelsTouchesInView = false
         table.addGestureRecognizer(tapGesture)
@@ -94,43 +93,61 @@ public class CreateSeminarViewController: UITableViewController {
         if isJoin {
             navigationItem.rightBarButtonItem?.title = "Join".localized()
             titleView.isEnabled = false
-            descriptionView.isEditable = false
-            if let a = data["type"] as? String {
-                chooser[0].value = AudienceViewController().data[getTypeIndex(value: a)]
+            if let a = data["title"] as? String {
+                titleView.isUserInteractionEnabled = false
+                titleView.text = a
             }
-            if let b = data["broadcast_type"] as? String {
-                chooser[1].value = TypeViewController().data[getBroadcastIndex(value: b)]
-            }
-            if let c = data["title"] as? String {
-                titleView.text = c
-            }
-            if let d = data["description"] as? String {
-                descriptionView.text = d
+            if let b = data["start"] as? String {
+                startView.isUserInteractionEnabled = false
+                startView.text = b
             }
         } else if !isJoin && !data.isEmpty {
             navigationItem.rightBarButtonItem?.title = "Start".localized()
             titleView.isEnabled = false
-            descriptionView.isEditable = false
-            if let a = data["type"] as? String {
-                chooser[0].value = AudienceViewController().data[getTypeIndex(value: a)]
-            }
-            if let b = data["broadcast_type"] as? String {
-                chooser[1].value = TypeViewController().data[getBroadcastIndex(value: b)]
-            }
-            if let c = data["title"] as? String {
-                titleView.text = c
+            if let a = data["title"] as? String {
+                titleView.text = a
             }
-            if let d = data["description"] as? String {
-                descriptionView.text = d
+            if let b = data["start"] as? String {
+                startView.text = b
             }
         }
         
         tableView = table
     }
     
+    private func configureDatePicker() {
+        // Set the date picker as the input view for the text field
+        startView.inputView = datePicker
+        
+        // Create a toolbar with a "Done" button
+        let toolbar = UIToolbar()
+        toolbar.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : UIColor.mainColor
+        toolbar.barTintColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : UIColor.mainColor
+        toolbar.sizeToFit()
+        let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneButtonTapped))
+        toolbar.setItems([doneButton], animated: true)
+        
+        // Set the toolbar as the accessory view for the text field
+        startView.inputAccessoryView = toolbar
+    }
+    
+    @objc private func doneButtonTapped() {
+        // Format the selected date and time
+        let formatter = DateFormatter()
+        formatter.dateStyle = .medium
+        formatter.timeStyle = .short
+        
+        // Update the text field with the formatted date and time
+        startTime = datePicker.date.currentTimeMillis()
+        startView.text = formatter.string(from: datePicker.date)
+        
+        // Dismiss the date picker
+        view.endEditing(true)
+    }
+    
+    
     @objc func dismissKeyboard() {
         titleView.resignFirstResponder()
-        descriptionView.resignFirstResponder()
     }
     
     @objc func didTapCancel(sender: AnyObject) {
@@ -138,32 +155,36 @@ public class CreateSeminarViewController: UITableViewController {
     }
     
     @objc func didTapRight(sender: Any?) {
-        let controller = SeminarViewController()
-        controller.isLive = !isJoin
+        let controller = VideoConferenceViewController()
+        controller.isInisiator = !isJoin
+//    TODO:    let controller = SeminarViewController()
+//    TODO:    controller.isLive = !isJoin
         if isJoin {
-            guard let by = data["by"] as? String else {
-                return
+//            guard let by = data["by"] as? String else {
+//                return
+//            }
+            if let dataBlog = data["blog"] as? String {
+                controller.roomId = dataBlog
             }
-            let dataBlog = data["blog"] as? String
 //            if dataBlog != nil {
-//                if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.getIsInitiatorJoin(p_broadcaster: by, p_category: "3", blog_id: dataBlog!)) {
+//                if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.joinVCallConference(blog_id: dataBlog!)) {
 //                    if response.getBody(key: CoreMessage_TMessageKey.ERRCOD) != "00" {
+//                            let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
+//                            imageView.tintColor = .white
+//                            let banner = FloatingNotificationBanner(title: "Conference call session hasn\'t started yet".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
+//                            banner.show()
+//                            return
+//                        }
+//                    } else {
 //                        let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
 //                        imageView.tintColor = .white
-//                        let banner = FloatingNotificationBanner(title: "Seminar session hasn\'t started yet".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
+//                        let banner = FloatingNotificationBanner(title: "No Network. Please try again.".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
 //                        banner.show()
 //                        return
 //                    }
-//                } else {
-//                    let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
-//                    imageView.tintColor = .white
-//                    let banner = FloatingNotificationBanner(title: "No Network. Please try again.".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
-//                    banner.show()
-//                    return
 //                }
-//            }
-            controller.data = by
-            controller.streamingData = data
+//      TODO:      controller.data = by
+//      TODO:      controller.streamingData = data
         } else {
             let goAudioCall = Nexilis.checkMicPermission()
             if !goAudioCall {
@@ -217,71 +238,59 @@ public class CreateSeminarViewController: UITableViewController {
             if !isJoin && !self.data.isEmpty {
                 data = self.data
             } else if self.data.isEmpty {
-                guard let streamingTitle = titleView.text else {
-                    return
-                }
-                guard let streamingDesc = descriptionView.text else {
+                guard let conferenceTitle = titleView.text else {
                     return
                 }
                 
-                let id = "LST\(Date().currentTimeMillis().toHex())"
+                let id = "CR\(Date().currentTimeMillis().toHex())"
                 
-                data["title"] = streamingTitle
-                data["description"] = streamingDesc
+                data["title"] = conferenceTitle
+//         TODO:        data["start"]
                 data["by"] = User.getMyPin() ?? ""
                 data["time"] = Date().currentTimeMillis()
                 data["blog"] = id
                 
-                if streamingTitle.trimmingCharacters(in: .whitespaces).isEmpty {
-                    let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
-                    imageView.tintColor = .white
-                    let banner = FloatingNotificationBanner(title: "Seminar title can't be empty".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
-                    banner.show()
-                    return
-                } else if streamingDesc.trimmingCharacters(in: .whitespaces).isEmpty {
+                if conferenceTitle.trimmingCharacters(in: .whitespaces).isEmpty {
                     let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
                     imageView.tintColor = .white
-                    let banner = FloatingNotificationBanner(title: "Seminar description can't be empty".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
+                    let banner = FloatingNotificationBanner(title: "Video Conference title can't be empty".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
                     banner.show()
                     return
                 }
-                
-                var type: String = "0" // Friend
-                let groups: [String] = groups.map{ $0.id }
+//                
+//                var type: String = "0" // Friend
                 let members: [String] = users.map{ $0.pin }
-                switch chooser[0].id {
-                case 0:
-                    type = "7"
-                case 1:
-                    type = "8"
-                case 2:
-                    type = "3"
-                case 3:
-                    type = "6"
-                case 4:
-                    type = "5"
-                default:
-                    type = "0"
-                }
-                
-                data["groups"] = groups
+//                switch chooser[0].id {
+//                case 0:
+//                    type = "7"
+//                case 1:
+//                    type = "8"
+//                case 2:
+//                    type = "3"
+//                case 3:
+//                    type = "6"
+//                case 4:
+//                    type = "5"
+//                default:
+//                    type = "0"
+//                }
+//                
                 data["members"] = members
-                data["type"] = type
                 
-                var notif: String = "0"
-                switch chooser[1].id {
-                case 0:
-                    notif = "1"
-                default:
-                    notif = "2"
-                }
+//                var notif: String = "0"
+//                switch chooser[1].id {
+//                case 0:
+//                    notif = "1"
+//                default:
+//                    notif = "2"
+//                }
                 
-                data["broadcast_type"] = notif
+//                data["broadcast_type"] = notif
                 guard let json = String(data: try! JSONSerialization.data(withJSONObject: data, options: []), encoding: String.Encoding.utf8) else {
                     return
                 }
                 
-                if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.createSeminar(title: "1~\(data["title"] ?? "")", type: data["type"] as! String, category: "4", notifType: data["broadcast_type"] as! String, blogId: data["blog"] as! String, data: json)) {
+                if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.createVCallConference(blog_id: data["blog"] as! String, data: json)) {
                     if response.getBody(key: CoreMessage_TMessageKey.ERRCOD) != "00" {
                         let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
                         imageView.tintColor = .white
@@ -297,75 +306,65 @@ public class CreateSeminarViewController: UITableViewController {
                     return
                 }
                 
+//                if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.startVCallConference(blog_id: data["blog"] as! String, time: data["time"] as! String), timeout: 30 * 1000) {
+//                    if response.getBody(key: CoreMessage_TMessageKey.ERRCOD) != "00" {
+//                        let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
+//                        imageView.tintColor = .white
+//                        let banner = FloatingNotificationBanner(title: "Server Busy. Please try again.".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
+//                        banner.show()
+//                        return
+//                    }
+//                } else {
+//                    let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
+//                    imageView.tintColor = .white
+//                    let banner = FloatingNotificationBanner(title: "No Network. Please try again.".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
+//                    banner.show()
+//                    return
+//                }
+                
 //                Nexilis.saveMessageBot(textMessage: json, blog_id: data["blog"] as? String ?? "", attachment_type: "26")
                 NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
             }
-            controller.data = User.getMyPin()!
-            controller.streamingData = data
+//          TODO:  controller.data = User.getMyPin()!
+//          TODO:  controller.streamingData = data
+            controller.roomId = data["blog"] as! String
         }
+//       TODO: navigationController?.show(controller, sender: nil)
         navigationController?.show(controller, sender: nil)
+//        navigationController?.dismiss(animated: true, completion: nil)
     }
     
-    private func getTypeIndex(value: String) -> Int {
-        var type = 0
-        switch value {
-        case "7":
-            type = 0
-        case "8":
-            type = 1
-        case "3":
-            type = 2
-        case "6":
-            type = 3
-        case "5":
-            type = 4
-        default:
-            type = 0
-        }
-        return type
-    }
-    
-    private func getBroadcastIndex(value: String) -> Int {
-        var notif = 0
-        switch value {
-        case "1":
-            notif = 0
-        default:
-            notif = 1
-        }
-        return notif
-    }
     
     // MARK: - Table view data source
     
     public override func numberOfSections(in tableView: UITableView) -> Int {
+        if isJoin {
+            return sections.count - 1
+        }
         return sections.count
     }
     
     public override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
         switch sections[section] {
-        case .chooser:
-            return 2
-        case .users:
+        case .participants:
             return users.count + 1
-        case .groups:
-            return groups.count + 1
         default:
             return 1
         }
     }
     
     public override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
-        switch sections[indexPath.section] {
-        case .description:
-            return 100
-        default:
-            return 44
-        }
+        return 44
+//        switch sections[indexPath.section] {
+//        case .description:
+//            return 100
+//        default:
+//            return 44
+//        }
     }
     
     public override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
-        if isJoin, sections[indexPath.section] == .chooser {
+        if isJoin, sections[indexPath.section] == .participants {
             return nil
         }
         return indexPath
@@ -374,63 +373,54 @@ public class CreateSeminarViewController: UITableViewController {
     public override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
         tableView.deselectRow(at: indexPath, animated: true)
         switch sections[indexPath.section] {
-        case .chooser:
-            if indexPath.row == 0 {
-                if isJoin || (!isJoin && !data.isEmpty){
-                    return
+        case .start:
+            if !isJoin {
+                sections.append(.participants)
+                if users.count != 0{
+                    users.removeAll()
                 }
-                let chooser = chooser[indexPath.row]
-                let controller = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "audienceView") as AudienceViewController
-                controller.selected = chooser.value
-                controller.isDismiss = { [weak self] index in
-                    chooser.id = index
-                    chooser.value = AudienceViewController().data[index]
-                    guard let sec = self?.sections else {
-                        return
-                    }
-                    if sec.count > 4 {
-                        self?.sections.removeLast()
-                    }
-                    if chooser.value == "Group".localized() {
-                        self?.sections.append(.groups)
-                        if self?.users.count != 0 {
-                            self?.users.removeAll()
-                        }
-                    } else if chooser.value == "User".localized() {
-                        self?.sections.append(.users)
-                        if self?.groups.count != 0{
-                            self?.groups.removeAll()
-                        }
-                    } else {
-                        if self?.users.count != 0 {
-                            self?.users.removeAll()
-                        } else if self?.groups.count != 0{
-                            self?.groups.removeAll()
-                        }
-                    }
-                    DispatchQueue.main.async {
-                        tableView.reloadData()
-                    }
-                }
-                navigationController?.show(controller, sender: nil)
-            } else if indexPath.row == 1 {
-                if isJoin || (!isJoin && !data.isEmpty){
-                    return
-                }
-                let chooser = chooser[indexPath.row]
-                let controller = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "typeView") as TypeViewController
-                controller.selected = chooser.value
-                controller.isDismiss = { index in
-                    chooser.id = index
-                    chooser.value = TypeViewController().data[index]
-                    DispatchQueue.main.async {
-                        tableView.reloadRows(at: [indexPath], with: .automatic)
-                    }
-                }
-                navigationController?.show(controller, sender: nil)
             }
-        case .users:
-            if indexPath.row == 0 {
+//            if indexPath.row == 0 {
+//                if isJoin || (!isJoin && !data.isEmpty){
+//                    return
+//                }
+//                let chooser = chooser[indexPath.row]
+//                let controller = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "audienceView") as AudienceViewController
+//                controller.selected = chooser.value
+//                controller.isDismiss = { [weak self] index in
+//                    chooser.id = index
+//                    chooser.value = AudienceViewController().data[index]
+//                    guard let sec = self?.sections else {
+//                        return
+//                    }
+//                    if sec.count > 4 {
+//                        self?.sections.removeLast()
+//                    }
+//                    if chooser.value == "Group".localized() {
+//                        self?.sections.append(.groups)
+//                        if self?.users.count != 0 {
+//                            self?.users.removeAll()
+//                        }
+//                    } else if chooser.value == "User".localized() {
+//                        self?.sections.append(.users)
+//                        if self?.groups.count != 0{
+//                            self?.groups.removeAll()
+//                        }
+//                    } else {
+//                        if self?.users.count != 0 {
+//                            self?.users.removeAll()
+//                        } else if self?.groups.count != 0{
+//                            self?.groups.removeAll()
+//                        }
+//                    }
+//                    DispatchQueue.main.async {
+//                        tableView.reloadData()
+//                    }
+//                }
+//                navigationController?.show(controller, sender: nil)
+//            }
+        case .participants:
+            if indexPath.row == 0 && !isJoin {
                 let controller = QmeraUserChooserViewController()
                 controller.ignored.append(contentsOf: users)
                 controller.isDismiss = { users in
@@ -438,15 +428,6 @@ public class CreateSeminarViewController: UITableViewController {
                 }
                 navigationController?.show(controller, sender: nil)
             }
-        case .groups:
-            if indexPath.row == 0 {
-                let controller = QmeraGroupStreamingViewController()
-                controller.ignored.append(contentsOf: groups)
-                controller.isDismiss = { groups in
-                    self.groups.append(contentsOf: groups)
-                }
-                navigationController?.show(controller, sender: nil)
-            }
         default:
             return
         }
@@ -459,29 +440,25 @@ public class CreateSeminarViewController: UITableViewController {
         cell.accessoryType = .none
         cell.selectionStyle = .none
         switch sections[indexPath.section] {
-        case .chooser:
-            var content = cell.defaultContentConfiguration()
-            let data = chooser[indexPath.row]
-            content.text = data.title
-            content.secondaryText = data.value
-            content.textProperties.font = UIFont.systemFont(ofSize: 14)
-            content.secondaryTextProperties.color = .systemGray
-            content.secondaryTextProperties.font = UIFont.systemFont(ofSize: 14)
-            content.prefersSideBySideTextAndSecondaryText = true
-            cell.contentConfiguration = content
-            cell.accessoryType = .disclosureIndicator
-            cell.selectionStyle = .default
+        case .start:
+//            var content = cell.defaultContentConfiguration()
+//            let data = chooser[indexPath.row]
+//            content.text = data.title
+//            content.secondaryText = data.value
+//            content.textProperties.font = UIFont.systemFont(ofSize: 14)
+//            content.secondaryTextProperties.color = .systemGray
+//            content.secondaryTextProperties.font = UIFont.systemFont(ofSize: 14)
+//            content.prefersSideBySideTextAndSecondaryText = true
+//            cell.contentConfiguration = content
+//            cell.accessoryType = .disclosureIndicator
+//            cell.selectionStyle = .default
+            cell.contentView.addSubview(startView)
+            startView.anchor(top: cell.topAnchor, left: cell.leftAnchor, bottom: cell.bottomAnchor, right: cell.rightAnchor, paddingLeft: 20, paddingRight: 20)
+            configureDatePicker()
         case .title:
             cell.contentView.addSubview(titleView)
             titleView.anchor(top: cell.topAnchor, left: cell.leftAnchor, bottom: cell.bottomAnchor, right: cell.rightAnchor, paddingLeft: 20, paddingRight: 20)
-        case .description:
-            let stack = UIStackView()
-            stack.axis = .vertical
-            stack.distribution = .fill
-            cell.contentView.addSubview(stack)
-            stack.anchor(top: cell.topAnchor, left: cell.leftAnchor, bottom: cell.bottomAnchor, right: cell.rightAnchor, paddingTop: 8, paddingLeft: 20, paddingBottom: 8, paddingRight: 20)
-            stack.addArrangedSubview(descriptionView)
-        case .users:
+        case .participants:
             var content = cell.defaultContentConfiguration()
             if indexPath.row == 0 {
                 content.image = UIImage(systemName: "plus.circle.fill")
@@ -499,40 +476,17 @@ public class CreateSeminarViewController: UITableViewController {
                 content.textProperties.font = UIFont.systemFont(ofSize: 14)
             }
             cell.contentConfiguration = content
-        case .groups:
-            var content = cell.defaultContentConfiguration()
-            if indexPath.row == 0 {
-                content.image = UIImage(systemName: "plus.circle.fill")
-                content.imageProperties.tintColor = .mainColor
-                content.text = "Add group".localized()
-                content.textProperties.font = UIFont.systemFont(ofSize: 14)
-                cell.accessoryType = .disclosureIndicator
-                cell.selectionStyle = .default
-            } else {
-                let data = groups[indexPath.row - 1]
-                getImage(name: data.profile, placeholderImage: UIImage(named: "Conversation---Purple", in: Bundle.resourceBundle(for: Nexilis.self), with: nil), isCircle: true, tableView: tableView, indexPath: indexPath) { result, isDownloaded, image in
-                    content.image = image
-                }
-                content.text = data.name
-                content.textProperties.font = UIFont.systemFont(ofSize: 14)
-            }
-            cell.contentConfiguration = content
         }
         return cell
     }
     
     public override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
         switch sections[indexPath.section] {
-        case .users:
+        case .participants:
             if (editingStyle == .delete) {
                 users.remove(at: indexPath.row - 1)
                 tableView.deleteRows(at: [indexPath], with: .automatic)
             }
-        case .groups:
-            if (editingStyle == .delete) {
-                groups.remove(at: indexPath.row - 1)
-                tableView.deleteRows(at: [indexPath], with: .automatic)
-            }
         default:
             return
         }
@@ -578,7 +532,7 @@ extension CreateSeminarViewController: UITextViewDelegate {
     
     public func textViewDidEndEditing(_ textView: UITextView) {
         if textView.text.isEmpty {
-            textView.text = "Description".localized()
+            textView.text = "Title".localized()
             textView.textColor = UIColor.lightGray
         }
     }

+ 1 - 1
StreamShield/Podfile

@@ -6,6 +6,6 @@ target 'StreamShield' do
   use_frameworks!
 
   # Pods for StreamShield
-  pod 'nuSDKService', '~> 3.0.1'
+  pod 'nuSDKService', '~> 4.0.5'
 
 end