Explorar o código

add feature search in editor

alqindiirsyam %!s(int64=2) %!d(string=hai) anos
pai
achega
9604519060

+ 6 - 2
appbuilder-ios/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="21225" 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="21507" 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="21207"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
         <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"/>
@@ -2587,12 +2587,14 @@
                         <outlet property="buttonSendPhoto" destination="3ef-CD-2Pj" id="fzG-Y0-H3l"/>
                         <outlet property="buttonSendSticker" destination="Y6s-g6-F6l" id="3Ar-0n-M14"/>
                         <outlet property="constraintBottomAttachment" destination="tbE-PI-oI9" id="zwJ-JT-Tbq"/>
+                        <outlet property="constraintBottomTableViewWithTextfield" destination="6PR-yi-4lY" id="hia-Wx-6hv"/>
                         <outlet property="constraintLeftTextField" destination="jx0-o5-Ja0" id="1hP-FQ-qYY"/>
                         <outlet property="constraintTopTextField" destination="43A-hi-qkj" id="UCq-4N-QAW"/>
                         <outlet property="constraintViewTextField" destination="kBx-ys-A1s" id="A9C-IX-cZ2"/>
                         <outlet property="heightTextFieldSend" destination="BM5-gS-VIN" id="Ioh-Xm-Kd0"/>
                         <outlet property="tableChatView" destination="tSE-6b-2zD" id="qeC-k9-MJz"/>
                         <outlet property="textFieldSend" destination="Zwh-BN-IXY" id="Y3X-Y9-Tg4"/>
+                        <outlet property="viewAttachment" destination="UMZ-Cg-DEH" id="oNr-Nt-Yjk"/>
                         <outlet property="viewButton" destination="Xda-XC-yEA" id="SfN-gB-RKH"/>
                         <outlet property="viewTextfield" destination="yLm-Hp-3ZU" id="oLC-X1-VKP"/>
                     </connections>
@@ -2750,11 +2752,13 @@
                         <outlet property="buttonSendPhoto" destination="acF-7N-m3T" id="amJ-hi-fyj"/>
                         <outlet property="buttonSendSticker" destination="w8i-CY-6go" id="oZz-WV-6bq"/>
                         <outlet property="constraintBottomAttachment" destination="7cj-we-UtI" id="z0B-86-TwT"/>
+                        <outlet property="constraintBottomTableViewWithTextfield" destination="LOl-kg-Tex" id="aqm-eZ-phZ"/>
                         <outlet property="constraintTopTextField" destination="rwD-sI-rSc" id="d82-qJ-lee"/>
                         <outlet property="constraintViewTextField" destination="bT4-KC-g5c" id="KVz-UY-FuX"/>
                         <outlet property="heightTextFieldSend" destination="8Zi-nS-Q7O" id="V0u-tl-hcY"/>
                         <outlet property="tableChatView" destination="pVj-YY-BAA" id="0Nc-wF-dmw"/>
                         <outlet property="textFieldSend" destination="bW3-fS-6Qv" id="NWj-px-tv0"/>
+                        <outlet property="viewAttachment" destination="ZXb-nM-tPm" id="Bbu-bM-wLZ"/>
                         <outlet property="viewButton" destination="rs3-vX-pDB" id="4yF-Hx-qVU"/>
                         <outlet property="viewTextfield" destination="fnQ-OG-1Lg" id="KTx-21-3J7"/>
                     </connections>

+ 3 - 1
appbuilder-ios/NexilisLite/NexilisLite/Resource/id.lproj/Localizable.strings

@@ -1,4 +1,4 @@
-/* 
+/*
   Localizable.string.strings
   QmeraLite
 
@@ -187,3 +187,5 @@
 "Invalid OTP" = "OTP tidak valid";
 "Successfully add friend" = "Berhasil menambahkan teman";
 "Login to Web" = "Masuk ke Web";
+"of" = "dari";
+"matches" = "cocok";

+ 11 - 3
appbuilder-ios/NexilisLite/NexilisLite/Source/Extension.swift

@@ -574,7 +574,7 @@ extension String {
         return length
     }
     
-    public func richText(isEditing: Bool = false, first: Int = 0, last: Int = 0) -> NSAttributedString {
+    public func richText(isEditing: Bool = false, first: Int = 0, last: Int = 0, isSearching: Bool = false, textSearch: String = "") -> NSAttributedString {
         let font = UIFont.systemFont(ofSize: 12)
         let textUTF8 = String(self.utf8)
         let finalText = NSMutableAttributedString(string: textUTF8, attributes: [NSAttributedString.Key.font: font])
@@ -718,6 +718,14 @@ extension String {
                     }
                 }
             }
+            if isSearching {
+                let attributedText = NSMutableAttributedString(string: finalText.string) // 1
+                let range = NSString(string: finalText.string).range(of: textSearch, options: .caseInsensitive) // 2
+                let highlightColor = UIColor.systemYellow // 3
+                let highlightedAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.backgroundColor: highlightColor] // 4
+                
+                finalText.addAttributes(highlightedAttributes, range: range) // 5
+            }
         }
         
         return finalText
@@ -804,7 +812,7 @@ extension Bundle {
 }
 
 //extension UIFont {
-//    
+//
 //    static func register(from url: URL) throws {
 //        guard let fontDataProvider = CGDataProvider(url: url as CFURL) else {
 //            throw fatalError("Could not create font data provider for \(url).")
@@ -815,7 +823,7 @@ extension Bundle {
 //            throw error!.takeUnretainedValue()
 //        }
 //    }
-//    
+//
 //}
 
 extension UIButton {

+ 341 - 90
appbuilder-ios/NexilisLite/NexilisLite/Source/View/Chat/EditorGroup.swift

@@ -30,6 +30,8 @@ public class EditorGroup: UIViewController {
     @IBOutlet var constraintBottomAttachment: NSLayoutConstraint!
     @IBOutlet var viewTextfield: UIView!
     @IBOutlet weak var buttonAckConfidential: UIButton!
+    @IBOutlet weak var constraintBottomTableViewWithTextfield: NSLayoutConstraint!
+    @IBOutlet weak var viewAttachment: UIStackView!
     public var dataGroup: [String: Any?] = [:]
     public var dataTopic: [String: Any?] = [:]
     var dataMessages: [[String: Any?]] = []
@@ -56,6 +58,7 @@ public class EditorGroup: UIViewController {
     var copySession = false
     var forwardSession = false
     var deleteSession = false
+    var isSearching = false
     let containerMultpileSelectSession = UIView()
     let viewSticker = UIView()
     let containerLink = UIView()
@@ -64,6 +67,14 @@ public class EditorGroup: UIViewController {
     let containerAction = UIView()
     var allowTyping = true
     let contactChatNav = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "contactChatNav") as! UINavigationController
+    var searchBar: UISearchBar!
+    var constraintBottomContainerMultpileSelectSession = NSLayoutConstraint()
+    var titleSearchMatches: UILabel!
+    var textSearch = ""
+    var countMatchesSearch = 0
+    var lastScrollIdxSearch = 0
+    var buttonUp: UIButton!
+    var buttonDown: UIButton!
     
     public override func viewDidDisappear(_ animated: Bool) {
         if self.isMovingFromParent {
@@ -159,6 +170,8 @@ public class EditorGroup: UIViewController {
     }
     
     private func setRightButtonItem() {
+        navigationItem.rightBarButtonItems = nil
+        navigationItem.rightBarButtonItem = nil
         let menu = UIMenu(title: "", children: [
             UIAction(title: "Delete Conversation".localized(), handler: {(_) in
                 let alert = UIAlertController(title: "", message: "Are you sure to delete all message in this conversation?".localized(), preferredStyle: .alert)
@@ -186,7 +199,27 @@ public class EditorGroup: UIViewController {
         ])
         if !isHistoryCC {
             let moreIcon = UIBarButtonItem(image: UIImage(systemName: "ellipsis", withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .regular, scale: .default)), menu: menu)
-            navigationItem.rightBarButtonItem = moreIcon
+            let buttonSearch = UIBarButtonItem(image: UIImage(systemName: "magnifyingglass", withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .regular, scale: .default)), style: .plain, target: self, action: #selector(search(sender:)))
+            navigationItem.rightBarButtonItems = [moreIcon,buttonSearch]
+        } else {
+            let buttonSearch = UIBarButtonItem(image: UIImage(systemName: "magnifyingglass", withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .regular, scale: .default)), style: .plain, target: self, action: #selector(search(sender:)))
+            navigationItem.rightBarButtonItem = buttonSearch
+        }
+    }
+    
+    @objc func search(sender: UIBarButtonItem) {
+        self.isSearching = true
+        if self.reffId != nil {
+            self.deleteReplyView()
+        }
+        DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
+            let cancelButton = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(self.cancelAction))
+            if !self.isHistoryCC {
+                self.navigationItem.rightBarButtonItems = nil
+            }
+            self.navigationItem.rightBarButtonItem = cancelButton
+            self.changeAppBar()
+            self.addMultipleSelectSession()
         }
     }
     
@@ -466,37 +499,49 @@ public class EditorGroup: UIViewController {
         let viewAppBar = UIView()
         viewAppBar.frame.size = CGSize(width: self.view.frame.size.width, height: 44)
         
-        let imageProfile = UIImageView(frame: CGRect(x: 0, y: 7, width: 30, height: 30))
-        imageProfile.circle()
-        imageProfile.clipsToBounds = true
-        viewAppBar.addSubview(imageProfile)
-        let pictureImage = dataGroup["image_id"]!
-        if (pictureImage as! String != "" && pictureImage != nil) {
-            imageProfile.setImage(name: pictureImage! as! String)
-            imageProfile.contentMode = .scaleAspectFill
-        } else {
-            imageProfile.image = UIImage(systemName: "person.3")
-            imageProfile.contentMode = .scaleAspectFit
-            imageProfile.backgroundColor = .lightGray
-        }
-        
-        let titleNavigation = UILabel(frame: CGRect(x: 35, y: 0, width: viewAppBar.frame.size.width - 150, height: 44))
-        viewAppBar.addSubview(titleNavigation)
-        if (dataGroup["official"] as! String == "1") {
-            if !isHistoryCC {
-                titleNavigation.set(image: UIImage(named: "ic_official_flag", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(.white), with: "  \(dataGroup["f_name"]!!) (\(dataTopic["title"]!!))", size: 15, y: -4)
+        if !isSearching {
+            let imageProfile = UIImageView(frame: CGRect(x: 0, y: 7, width: 30, height: 30))
+            imageProfile.circle()
+            imageProfile.clipsToBounds = true
+            viewAppBar.addSubview(imageProfile)
+            let pictureImage = dataGroup["image_id"]!
+            if (pictureImage as! String != "" && pictureImage != nil) {
+                imageProfile.setImage(name: pictureImage! as! String)
+                imageProfile.contentMode = .scaleAspectFill
             } else {
-                titleNavigation.text = (dataGroup["f_name"] as? String)! + " " + "Contact Center".localized()
+                imageProfile.image = UIImage(systemName: "person.3")
+                imageProfile.contentMode = .scaleAspectFit
+                imageProfile.backgroundColor = .lightGray
             }
+            
+            let titleNavigation = UILabel(frame: CGRect(x: 35, y: 0, width: viewAppBar.frame.size.width - 150, height: 44))
+            viewAppBar.addSubview(titleNavigation)
+            if (dataGroup["official"] as! String == "1") {
+                if !isHistoryCC {
+                    titleNavigation.set(image: UIImage(named: "ic_official_flag", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(.white), with: "  \(dataGroup["f_name"]!!) (\(dataTopic["title"]!!))", size: 15, y: -4)
+                } else {
+                    titleNavigation.text = (dataGroup["f_name"] as? String)! + " " + "Contact Center".localized()
+                }
+            } else {
+                titleNavigation.text = (dataGroup["f_name"] as? String)! + " (\(dataTopic["title"]!!))"
+            }
+            titleNavigation.textColor = .white
+            titleNavigation.font = UIFont.systemFont(ofSize: 12).bold
+            
+            navigationItem.titleView = viewAppBar
         } else {
-            titleNavigation.text = (dataGroup["f_name"] as? String)! + " (\(dataTopic["title"]!!))"
-        }
-        titleNavigation.textColor = .white
-        titleNavigation.font = UIFont.systemFont(ofSize: 12).bold
-        
-        navigationItem.titleView = viewAppBar
-        
-        if copySession || forwardSession || deleteSession {
+            searchBar = UISearchBar()
+            searchBar.autocapitalizationType = .none
+            searchBar.delegate = self
+            searchBar.searchTextField.tintColor = .mainColor
+            searchBar.barTintColor = .secondaryColor
+            searchBar.searchTextField.backgroundColor = .secondaryColor
+            searchBar.showsCancelButton = false
+            navigationItem.titleView = searchBar
+            self.definesPresentationContext = true
+        }
+        
+        if copySession || forwardSession || deleteSession || isSearching {
             navigationItem.hidesBackButton = true
             navigationController?.interactivePopGestureRecognizer?.isEnabled = false
         } else {
@@ -1007,11 +1052,15 @@ public class EditorGroup: UIViewController {
     }
     
     @objc func dismissKeyboard() {
-        textFieldSend.resignFirstResponder() // dismiss keyoard
-        if (self.constraintBottomAttachment.constant != 0.0) {
-            constraintBottomAttachment.constant = 0.0
-            self.viewSticker.removeConstraints(self.viewSticker.constraints)
-            self.viewSticker.removeFromSuperview()
+        if isSearching {
+            searchBar.resignFirstResponder()
+        } else {
+            textFieldSend.resignFirstResponder() // dismiss keyoard
+            if (self.constraintBottomAttachment.constant != 0.0) {
+                constraintBottomAttachment.constant = 0.0
+                self.viewSticker.removeConstraints(self.viewSticker.constraints)
+                self.viewSticker.removeFromSuperview()
+            }
         }
     }
     
@@ -1031,13 +1080,21 @@ public class EditorGroup: UIViewController {
             
             if self.constraintViewTextField.constant != keyboardHeight - 60 {
                 self.constraintViewTextField.constant = keyboardHeight - 60
+                if isSearching {
+                    self.constraintViewTextField.constant = self.constraintViewTextField.constant + 60
+                    self.constraintBottomContainerMultpileSelectSession.constant = -keyboardHeight
+                }
                 UIView.animate(withDuration: TimeInterval(duration), animations: {
                     self.view.layoutIfNeeded()
                 })
-                if (self.currentIndexpath != nil) {
-                    self.tableChatView.scrollToRow(at: IndexPath(row: self.currentIndexpath!.row, section: self.currentIndexpath!.section), at: .none, animated: false)
-                } else {
+                if isSearching {
                     self.tableChatView.scrollToBottom()
+                } else {
+                    if (self.currentIndexpath != nil) {
+                        self.tableChatView.scrollToRow(at: IndexPath(row: self.currentIndexpath!.row, section: self.currentIndexpath!.section), at: .none, animated: false)
+                    } else {
+                        self.tableChatView.scrollToBottom()
+                    }
                 }
             }
         }
@@ -1049,6 +1106,7 @@ public class EditorGroup: UIViewController {
             let duration: CGFloat = info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat
             
             self.constraintViewTextField.constant = 0
+            self.constraintBottomContainerMultpileSelectSession.constant = 0
             UIView.animate(withDuration: TimeInterval(duration), animations: {
                 self.view.layoutIfNeeded()
             })
@@ -1828,21 +1886,32 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
             if self.removed {
                 return
             }
-            self.handleReply(indexPath: indexPath!)
+            if self.isSearching {
+                self.cancelAction()
+            }
+            DispatchQueue.main.asyncAfter(deadline: .now() + 0.35, execute: {
+                self.handleReply(indexPath: indexPath!)
+            })
         })
         let forward = UIAction(title: "Forward".localized(), image: UIImage(systemName: "arrowshape.turn.up.right.fill"), handler: {(_) in
             if self.removed {
                 return
             }
-            self.forwardSession = true
+            if self.isSearching {
+                self.cancelAction()
+            }
             if self.reffId != nil {
                 self.deleteReplyView()
             }
             DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
+                self.forwardSession = true
                 let cancelButton = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(self.cancelAction))
+                if !self.isHistoryCC {
+                    self.navigationItem.rightBarButtonItems = nil
+                }
                 self.navigationItem.rightBarButtonItem = cancelButton
                 self.changeAppBar()
-                let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as! String == dataMessages[indexPath!.row]["message_id"] as! String})
+                let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath!.row]["message_id"] as? String})
                 if idx != nil{
                     self.dataMessages[idx!]["isSelected"] = true
                 }
@@ -1854,15 +1923,21 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
             if self.removed {
                 return
             }
-            self.copySession = true
+            if self.isSearching {
+                self.cancelAction()
+            }
             if self.reffId != nil {
                 self.deleteReplyView()
             }
             DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
+                self.copySession = true
                 let cancelButton = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(self.cancelAction))
+                if !self.isHistoryCC {
+                    self.navigationItem.rightBarButtonItems = nil
+                }
                 self.navigationItem.rightBarButtonItem = cancelButton
                 self.changeAppBar()
-                let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as! String == dataMessages[indexPath!.row]["message_id"] as! String})
+                let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath!.row]["message_id"] as? String})
                 if idx != nil{
                     self.dataMessages[idx!]["isSelected"] = true
                 }
@@ -1884,15 +1959,21 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
             if self.removed {
                 return
             }
-            self.deleteSession = true
+            if self.isSearching {
+                self.cancelAction()
+            }
             if self.reffId != nil {
                 self.deleteReplyView()
             }
             DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
+                self.deleteSession = true
                 let cancelButton = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(self.cancelAction))
+                if !self.isHistoryCC {
+                    self.navigationItem.rightBarButtonItems = nil
+                }
                 self.navigationItem.rightBarButtonItem = cancelButton
                 self.changeAppBar()
-                let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as! String == dataMessages[indexPath!.row]["message_id"] as! String})
+                let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath!.row]["message_id"] as? String})
                 if idx != nil{
                     self.dataMessages[idx!]["isSelected"] = true
                 }
@@ -1939,6 +2020,25 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
                 self.forwardSession = false
             } else if self.deleteSession {
                 self.deleteSession = false
+            } else if self.isSearching {
+                self.countMatchesSearch = 0
+                self.isSearching = false
+            }
+            if self.viewTextfield.isHidden {
+                self.viewTextfield.isHidden = false
+            }
+            if self.viewAttachment.isHidden {
+                self.viewAttachment.isHidden = false
+            }
+            if !self.isSearching {
+                self.constraintBottomTableViewWithTextfield.constant = self.constraintBottomTableViewWithTextfield.constant + 70
+                DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: {
+                    if (self.currentIndexpath != nil) {
+                        self.tableChatView.scrollToRow(at: IndexPath(row: self.currentIndexpath!.row, section: self.currentIndexpath!.section), at: .none, animated: true)
+                    } else {
+                        self.tableChatView.scrollToBottom()
+                    }
+                })
             }
             let data = self.dataMessages.filter({ $0["isSelected"] as! Bool == true })
             for i in 0..<data.count {
@@ -1956,13 +2056,17 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
     }
     
     private func addMultipleSelectSession() {
+        viewTextfield.isHidden = true
+        viewAttachment.isHidden = true
+        constraintBottomTableViewWithTextfield.constant = constraintBottomTableViewWithTextfield.constant - 70
         view.addSubview(containerMultpileSelectSession)
         containerMultpileSelectSession.translatesAutoresizingMaskIntoConstraints = false
+        constraintBottomContainerMultpileSelectSession = containerMultpileSelectSession.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0)
         NSLayoutConstraint.activate([
             containerMultpileSelectSession.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
             containerMultpileSelectSession.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
-            containerMultpileSelectSession.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
-            containerMultpileSelectSession.heightAnchor.constraint(equalToConstant: 120)
+            constraintBottomContainerMultpileSelectSession,
+            containerMultpileSelectSession.heightAnchor.constraint(equalToConstant: 50)
         ])
         containerMultpileSelectSession.backgroundColor = .white
         addSubviewMultipleSession()
@@ -1984,52 +2088,104 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
         container.layer.shadowColor = UIColor.black.cgColor
         container.backgroundColor = .secondaryColor
         
-        let title = UILabel()
-        container.addSubview(title)
-        title.translatesAutoresizingMaskIntoConstraints = false
-        NSLayoutConstraint.activate([
-            title.centerXAnchor.constraint(equalTo: container.centerXAnchor),
-            title.centerYAnchor.constraint(equalTo:container.centerYAnchor),
-        ])
-        let countSelected = dataMessages.filter({ $0["isSelected"] as! Bool == true }).count
-        title.text = "\(countSelected) " + "Selected".localized()
-        title.textColor = .mainColor
-        title.font = UIFont.systemFont(ofSize: 15).bold
-        
-        let button = UIImageView()
-        container.addSubview(button)
-        button.translatesAutoresizingMaskIntoConstraints = false
-        NSLayoutConstraint.activate([
-            button.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 15),
-            button.centerYAnchor.constraint(equalTo:container.centerYAnchor),
-            button.widthAnchor.constraint(equalToConstant: 30),
-            button.heightAnchor.constraint(equalToConstant: 30),
-        ])
-        if copySession {
-            button.image = UIImage(systemName: "doc.on.doc")
-            if countSelected == 0{
-                button.tintColor = .gray
-            } else {
-                button.tintColor = .mainColor
-            }
-        } else if forwardSession {
-            button.image = UIImage(systemName: "arrowshape.turn.up.right")
-            if countSelected == 0{
-                button.tintColor = .gray
-            } else {
-                button.tintColor = .mainColor
-            }
-        } else if deleteSession {
-            button.image = UIImage(systemName: "trash")
-            if countSelected == 0{
-                button.tintColor = .gray
-            } else {
-                button.tintColor = .red
+        if !isSearching {
+            let title = UILabel()
+            container.addSubview(title)
+            title.translatesAutoresizingMaskIntoConstraints = false
+            NSLayoutConstraint.activate([
+                title.centerXAnchor.constraint(equalTo: container.centerXAnchor),
+                title.centerYAnchor.constraint(equalTo:container.centerYAnchor),
+            ])
+            let countSelected = dataMessages.filter({ $0["isSelected"] as! Bool == true }).count
+            title.text = "\(countSelected) " + "Selected".localized()
+            title.textColor = .mainColor
+            title.font = UIFont.systemFont(ofSize: 15).bold
+            
+            let button = UIImageView()
+            container.addSubview(button)
+            button.translatesAutoresizingMaskIntoConstraints = false
+            NSLayoutConstraint.activate([
+                button.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 15),
+                button.centerYAnchor.constraint(equalTo:container.centerYAnchor),
+                button.widthAnchor.constraint(equalToConstant: 30),
+                button.heightAnchor.constraint(equalToConstant: 30),
+            ])
+            if copySession {
+                button.image = UIImage(systemName: "doc.on.doc")
+                if countSelected == 0{
+                    button.tintColor = .gray
+                } else {
+                    button.tintColor = .mainColor
+                }
+            } else if forwardSession {
+                button.image = UIImage(systemName: "arrowshape.turn.up.right")
+                if countSelected == 0{
+                    button.tintColor = .gray
+                } else {
+                    button.tintColor = .mainColor
+                }
+            } else if deleteSession {
+                button.image = UIImage(systemName: "trash")
+                if countSelected == 0{
+                    button.tintColor = .gray
+                } else {
+                    button.tintColor = .red
+                }
             }
+            let buttonGesture = UITapGestureRecognizer(target: self, action: #selector(sessionAction))
+            button.isUserInteractionEnabled = true
+            button.addGestureRecognizer(buttonGesture)
+        } else {
+            buttonUp = UIButton()
+            container.addSubview(buttonUp)
+            buttonUp.translatesAutoresizingMaskIntoConstraints = false
+            NSLayoutConstraint.activate([
+                buttonUp.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 10),
+                buttonUp.centerYAnchor.constraint(equalTo:container.centerYAnchor),
+                buttonUp.widthAnchor.constraint(equalToConstant: 30),
+                buttonUp.heightAnchor.constraint(equalToConstant: 30),
+            ])
+            buttonUp.addTarget(self, action: #selector(upSearchText), for: .touchUpInside)
+            
+            buttonDown = UIButton()
+            container.addSubview(buttonDown)
+            buttonDown.translatesAutoresizingMaskIntoConstraints = false
+            NSLayoutConstraint.activate([
+                buttonDown.leadingAnchor.constraint(equalTo: buttonUp.trailingAnchor, constant: 15),
+                buttonDown.centerYAnchor.constraint(equalTo:container.centerYAnchor),
+                buttonDown.widthAnchor.constraint(equalToConstant: 30),
+                buttonDown.heightAnchor.constraint(equalToConstant: 30),
+            ])
+            buttonDown.addTarget(self, action: #selector(downSearchText), for: .touchUpInside)
+            
+            buttonUp.setImage(UIImage(systemName: "chevron.up"), for: .normal)
+            buttonUp.tintColor = .gray
+            
+            buttonDown.setImage(UIImage(systemName: "chevron.down"), for: .normal)
+            buttonDown.tintColor = .gray
+            
+            titleSearchMatches = UILabel()
+            container.addSubview(titleSearchMatches)
+            titleSearchMatches.translatesAutoresizingMaskIntoConstraints = false
+            NSLayoutConstraint.activate([
+                titleSearchMatches.centerXAnchor.constraint(equalTo: container.centerXAnchor),
+                titleSearchMatches.centerYAnchor.constraint(equalTo:container.centerYAnchor),
+            ])
+            titleSearchMatches.textColor = .mainColor
+            titleSearchMatches.font = UIFont.systemFont(ofSize: 15.0).bold
+            titleSearchMatches.isHidden = true
+            DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: {
+                self.searchBar.becomeFirstResponder()
+            })
         }
-        let buttonGesture = UITapGestureRecognizer(target: self, action: #selector(sessionAction))
-        button.isUserInteractionEnabled = true
-        button.addGestureRecognizer(buttonGesture)
+    }
+    
+    @objc func upSearchText() {
+        scrollToFirstSearchMessage(indexScroll: lastScrollIdxSearch + 1)
+    }
+    
+    @objc func downSearchText() {
+        scrollToFirstSearchMessage(indexScroll: lastScrollIdxSearch - 1)
     }
     
     @objc func sessionAction() {
@@ -2864,6 +3020,13 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
             }
         }
         
+        if isSearching && textSearch.count > 1 {
+            messageText.attributedText = textChat!.richText(isSearching: true, textSearch: textSearch)
+            if textChat!.lowercased().contains(textSearch) {
+                countMatchesSearch += 1
+            }
+        }
+        
         let stringDate = (dataMessages[indexPath.row]["server_date"] as! String)
         let date = Date(milliseconds: Int64(stringDate)!)
         let formatter = DateFormatter()
@@ -3700,4 +3863,92 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
             imageSticker.heightAnchor.constraint(equalToConstant: 30).isActive = true
         }
     }
+    
+    func scrollToFirstSearchMessage(indexScroll: Int = 1) {
+        if textSearch.count < 2 {
+            return
+        }
+        var lastIndex = 0
+        let messageTextForSearch: [[String: Any?]] = self.dataMessages.reversed()
+        for idx in 0..<messageTextForSearch.count {
+            if (messageTextForSearch[idx]["message_text"] as! String).lowercased().contains(textSearch) {
+                lastIndex += 1
+                if lastIndex < indexScroll {
+                    continue
+                }
+                lastScrollIdxSearch = lastIndex
+                let section = self.dataDates.firstIndex(of: messageTextForSearch[idx]["chat_date"] as! String)
+                if section == nil {
+                    return
+                }
+                let row = self.dataMessages.filter({ $0["chat_date"] as! String == self.dataDates[section!]}).firstIndex(where: { $0["message_id"] as! String == messageTextForSearch[idx]["message_id"] as! String})
+                if row == nil {
+                    return
+                }
+                let indexPath = IndexPath(row: row!, section: section!)
+                self.tableChatView.scrollToRow(at: indexPath, at: .middle, animated: true)
+                DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
+                    if let cell = self.tableChatView.cellForRow(at: indexPath) {
+                        let containerMessage = cell.contentView.subviews[1]
+                        let idMe = UserDefaults.standard.string(forKey: "me") as String?
+                        if (messageTextForSearch[idx]["f_pin"] as? String == idMe) {
+                            containerMessage.backgroundColor = .blueBubbleColor.withAlphaComponent(0.3)
+                            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+                                if (messageTextForSearch[idx]["attachment_flag"] as? String == "11") {
+                                    containerMessage.backgroundColor = .clear
+                                } else {
+                                    containerMessage.backgroundColor = .blueBubbleColor
+                                }
+                            }
+                        } else {
+                            containerMessage.backgroundColor = .grayColor.withAlphaComponent(0.3)
+                            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+                                if (messageTextForSearch[idx]["attachment_flag"] as? String == "11") {
+                                    containerMessage.backgroundColor = .clear
+                                } else {
+                                    containerMessage.backgroundColor = .grayColor
+                                }
+                            }
+                        }
+                    }
+                }
+                titleSearchMatches.isHidden = false
+                if countMatchesSearch != 0 {
+                    if countMatchesSearch > 1 {
+                        titleSearchMatches.text = "\(lastScrollIdxSearch) " + "of".localized() + " \(countMatchesSearch) " + "matches".localized()
+                    } else {
+                        titleSearchMatches.text = "\(countMatchesSearch) " + "matches".localized()
+                    }
+                } else {
+                    titleSearchMatches.text = "Not found".localized()
+                }
+                if lastScrollIdxSearch == countMatchesSearch || countMatchesSearch == 0 {
+                    buttonUp.isEnabled = false
+                    buttonUp.tintColor = .gray
+                } else {
+                    buttonUp.isEnabled = true
+                    buttonUp.tintColor = .mainColor
+                }
+                if countMatchesSearch == 0 || lastScrollIdxSearch == 1 || countMatchesSearch == 1 {
+                    buttonDown.isEnabled = false
+                    buttonDown.tintColor = .gray
+                } else {
+                    buttonDown.isEnabled = true
+                    buttonDown.tintColor = .mainColor
+                }
+                break
+            }
+        }
+    }
+}
+
+extension EditorGroup: UISearchBarDelegate {
+    
+    public func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
+        textSearch = searchText
+        countMatchesSearch = 0
+        titleSearchMatches.isHidden = true
+        tableChatView.reloadData()
+        scrollToFirstSearchMessage()
+    }
 }

+ 422 - 170
appbuilder-ios/NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift

@@ -31,6 +31,8 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
     @IBOutlet var viewTextfield: UIView!
     @IBOutlet weak var buttonAckConfidential: UIButton!
     @IBOutlet weak var constraintLeftTextField: NSLayoutConstraint!
+    @IBOutlet weak var constraintBottomTableViewWithTextfield: NSLayoutConstraint!
+    @IBOutlet weak var viewAttachment: UIStackView!
     public var dataPerson: [String: String?] = [:]
     var dataMessages: [[String: Any?]] = []
     var dataDates: [String] = []
@@ -59,6 +61,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
     var copySession = false
     var forwardSession = false
     var deleteSession = false
+    var isSearching = false
     let containerMultpileSelectSession = UIView()
     let containerAction = UIView()
     var removed = false
@@ -78,6 +81,14 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
     var listTimerCredential: [String: Int] = [:]
     var timerCredential: [String: Timer] = [:]
     let contactChatNav = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "contactChatNav") as! UINavigationController
+    var searchBar: UISearchBar!
+    var constraintBottomContainerMultpileSelectSession = NSLayoutConstraint()
+    var titleSearchMatches: UILabel!
+    var textSearch = ""
+    var countMatchesSearch = 0
+    var lastScrollIdxSearch = 0
+    var buttonUp: UIButton!
+    var buttonDown: UIButton!
     
     public override func viewDidDisappear(_ animated: Bool) {
         if self.isMovingFromParent {
@@ -215,33 +226,29 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
     
     private func setRightButtonItem() {
         navigationItem.rightBarButtonItems = nil
-        var menu = UIMenu(title: "", children: [
-            UIAction(title: "Delete Conversation".localized(), handler: {(_) in
-                if !self.isContactCenter {
-                    let alert = UIAlertController(title: "", message: "Are you sure to delete all message in this conversation?".localized(), preferredStyle: .alert)
-                    alert.addAction(UIAlertAction(title: "Cancel".localized(), style: UIAlertAction.Style.default, handler: nil))
-                    alert.addAction(UIAlertAction(title: "Delete".localized(), style: .destructive, handler: {(_) in
-                        Database.shared.database?.inTransaction({ (fmdb, rollback) in
-                            _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE_SUMMARY", _where: "l_pin='\(self.dataPerson["f_pin"]!!)'")
-                            _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE", _where: "(f_pin='\(self.dataPerson["f_pin"]!!)' or l_pin='\(self.dataPerson["f_pin"]!!)') and (message_scope_id='3' or message_scope_id='18') and is_call_center = 0")
-                        })
-                        if self.fromNotification {
-                            self.didTapExit()
-                        } else {
-                            self.navigationController?.popViewController(animated: true)
-                        }
-//                        self.dataMessages.removeAll()
-//                        self.dataDates.removeAll()
-//                        self.tableChatView.reloadData()
-                    }))
-                    self.present(alert, animated: true, completion: nil)
-                }
-            })
-        ])
+        var menu = UIMenu()
         let exblock = User.getData(pin: self.dataPerson["f_pin"]!!)?.ex_block
         blocking = exblock == nil ? "0" : exblock!.isEmpty ? "0" : exblock!
         if blocking == "1" && self.dataPerson["f_pin"]!! != "-999" {
             menu = UIMenu(title: "", children: [
+                UIAction(title: "Search".localized(), handler: {(_) in
+                    self.isSearching = true
+                    if self.reffId != nil {
+                        self.deleteReplyView()
+                    }
+                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
+                        let cancelButton = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(self.cancelAction))
+                        if self.dataPerson["f_pin"] != "-999" && !self.isContactCenter {
+                            self.navigationItem.rightBarButtonItems = nil
+                        }
+                        self.navigationItem.rightBarButtonItem = cancelButton
+                        if self.isContactCenter || self.fromNotification {
+                            self.navigationItem.leftBarButtonItem = nil
+                        }
+                        self.changeAppBar()
+                        self.addMultipleSelectSession()
+                    }
+                }),
                 UIAction(title: "Unblock".localized(), handler: {(_) in
                     if !self.isContactCenter {
                         DispatchQueue.global().async {
@@ -306,6 +313,24 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
         } else if blocking == "0" {
             if self.dataPerson["f_pin"]!! != "-999"{
                 menu = UIMenu(title: "", children: [
+                    UIAction(title: "Search".localized(), handler: {(_) in
+                        self.isSearching = true
+                        if self.reffId != nil {
+                            self.deleteReplyView()
+                        }
+                        DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
+                            let cancelButton = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(self.cancelAction))
+                            if self.dataPerson["f_pin"] != "-999" && !self.isContactCenter {
+                                self.navigationItem.rightBarButtonItems = nil
+                            }
+                            self.navigationItem.rightBarButtonItem = cancelButton
+                            if self.isContactCenter || self.fromNotification {
+                                self.navigationItem.leftBarButtonItem = nil
+                            }
+                            self.changeAppBar()
+                            self.addMultipleSelectSession()
+                        }
+                    }),
                     UIAction(title: "Block".localized(), handler: {(_) in
                         if !self.isContactCenter {
                             DispatchQueue.global().async {
@@ -366,6 +391,24 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                 ])
             } else {
                 menu = UIMenu(title: "", children: [
+                    UIAction(title: "Search".localized(), handler: {(_) in
+                        self.isSearching = true
+                        if self.reffId != nil {
+                            self.deleteReplyView()
+                        }
+                        DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
+                            let cancelButton = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(self.cancelAction))
+                            if self.dataPerson["f_pin"] != "-999" && !self.isContactCenter {
+                                self.navigationItem.rightBarButtonItems = nil
+                            }
+                            self.navigationItem.rightBarButtonItem = cancelButton
+                            if self.isContactCenter || self.fromNotification {
+                                self.navigationItem.leftBarButtonItem = nil
+                            }
+                            self.changeAppBar()
+                            self.addMultipleSelectSession()
+                        }
+                    }),
                     UIAction(title: "Delete Conversation".localized(), handler: {(_) in
                         if !self.isContactCenter {
                             let alert = UIAlertController(title: "", message: "Are you sure to delete all message in this conversation?".localized(), preferredStyle: .alert)
@@ -575,116 +618,127 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
         let viewAppBar = UIView()
         viewAppBar.frame.size = CGSize(width: self.view.frame.size.width, height: 44)
         
-        let imageProfile = UIImageView(frame: CGRect(x: 0, y: 7, width: 30, height: 30))
-        imageProfile.circle()
-        imageProfile.clipsToBounds = true
-        let pictureImage = dataPerson["picture"]!
-        var count = 0
-        if isContactCenter {
-            if fPinContacCenter.isEmpty && isRequestContactCenter {
-                getImage(name: dataPerson["picture"]!!, placeholderImage: UIImage(systemName: "person.circle.fill")!) { result, isDownloaded, image in
-                    imageProfile.image = image
-                }
-                viewAppBar.addSubview(imageProfile)
-            } else {
-                if users.count == 1 {
-                    viewAppBar.addSubview(imageProfile)
-                    getImage(name: users[0].thumb, placeholderImage: UIImage(systemName: "person.circle.fill")!) { result, isDownloaded, image in
+        if !isSearching {
+            let imageProfile = UIImageView(frame: CGRect(x: 0, y: 7, width: 30, height: 30))
+            imageProfile.circle()
+            imageProfile.clipsToBounds = true
+            let pictureImage = dataPerson["picture"]!
+            var count = 0
+            if isContactCenter {
+                if fPinContacCenter.isEmpty && isRequestContactCenter {
+                    getImage(name: dataPerson["picture"]!!, placeholderImage: UIImage(systemName: "person.circle.fill")!) { result, isDownloaded, image in
                         imageProfile.image = image
-                        imageProfile.contentMode = .scaleAspectFit
                     }
+                    viewAppBar.addSubview(imageProfile)
                 } else {
-                    for user in users {
-                        if count == 3 {
-                            count += 1
-                            continue
+                    if users.count == 1 {
+                        viewAppBar.addSubview(imageProfile)
+                        getImage(name: users[0].thumb, placeholderImage: UIImage(systemName: "person.circle.fill")!) { result, isDownloaded, image in
+                            imageProfile.image = image
+                            imageProfile.contentMode = .scaleAspectFit
                         }
-                        if count == 0 {
-                            let pictures = UIImageView(frame: CGRect(x: 0, y: 7, width: 30, height: 30))
-                            pictures.circle()
-                            pictures.clipsToBounds = true
-                            viewAppBar.addSubview(pictures)
-                            getImage(name: user.thumb, placeholderImage: UIImage(systemName: "person.circle.fill")!) { result, isDownloaded, image in
-                                pictures.image = image
-                                pictures.contentMode = .scaleAspectFit
+                    } else {
+                        for user in users {
+                            if count == 3 {
+                                count += 1
+                                continue
                             }
-                        } else {
-                            let pictures = UIImageView(frame: CGRect(x: count * 20 , y: 7, width: 30, height: 30))
-                            pictures.circle()
-                            pictures.clipsToBounds = true
-                            viewAppBar.addSubview(pictures)
-                            getImage(name: user.thumb, placeholderImage: UIImage(systemName: "person.circle.fill")!) { result, isDownloaded, image in
-                                pictures.image = image
-                                pictures.contentMode = .scaleAspectFit
+                            if count == 0 {
+                                let pictures = UIImageView(frame: CGRect(x: 0, y: 7, width: 30, height: 30))
+                                pictures.circle()
+                                pictures.clipsToBounds = true
+                                viewAppBar.addSubview(pictures)
+                                getImage(name: user.thumb, placeholderImage: UIImage(systemName: "person.circle.fill")!) { result, isDownloaded, image in
+                                    pictures.image = image
+                                    pictures.contentMode = .scaleAspectFit
+                                }
+                            } else {
+                                let pictures = UIImageView(frame: CGRect(x: count * 20 , y: 7, width: 30, height: 30))
+                                pictures.circle()
+                                pictures.clipsToBounds = true
+                                viewAppBar.addSubview(pictures)
+                                getImage(name: user.thumb, placeholderImage: UIImage(systemName: "person.circle.fill")!) { result, isDownloaded, image in
+                                    pictures.image = image
+                                    pictures.contentMode = .scaleAspectFit
+                                }
                             }
+                            count += 1
                         }
-                        count += 1
                     }
                 }
-            }
-        } else if dataPerson["f_pin"]!! == "-999" {
-            viewAppBar.addSubview(imageProfile)
-            if Utils.getIconDock() != nil {
-                let dataImage = try? Data(contentsOf: URL(string: Utils.getUrlDock()!)!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
-                if dataImage != nil {
-                    imageProfile.image = UIImage(data: dataImage!)
+            } else if dataPerson["f_pin"]!! == "-999" {
+                viewAppBar.addSubview(imageProfile)
+                if Utils.getIconDock() != nil {
+                    let dataImage = try? Data(contentsOf: URL(string: Utils.getUrlDock()!)!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
+                    if dataImage != nil {
+                        imageProfile.image = UIImage(data: dataImage!)
+                    }
+                } else {
+                    imageProfile.image = UIImage(named: "pb_button", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
                 }
-            } else {
-                imageProfile.image = UIImage(named: "pb_button", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
+                imageProfile.contentMode = .scaleAspectFit
             }
-            imageProfile.contentMode = .scaleAspectFit
-        }
-        else if (pictureImage != "" && pictureImage != nil) {
-            viewAppBar.addSubview(imageProfile)
-            imageProfile.setImage(name: pictureImage!)
-            imageProfile.contentMode = .scaleAspectFill
-        } else {
-            viewAppBar.addSubview(imageProfile)
-            imageProfile.image = UIImage(systemName: "person")
-            imageProfile.contentMode = .scaleAspectFit
-            imageProfile.backgroundColor = .lightGray
-        }
-        
-        var titleNavigation = UILabel(frame: CGRect(x: 35, y: 0, width: viewAppBar.frame.size.width - 150, height: 44))
-        if dataPerson["f_pin"]!! != "-999"  && !isContactCenter && (blocking == "-1" || blocking == "1") {
-            titleNavigation = UILabel(frame: CGRect(x: 35, y: 0, width: viewAppBar.frame.size.width - 250, height: 44))
-        } else if isContactCenter {
-            if users.count > 0 {
-                titleNavigation = UILabel(frame: CGRect(x: 35 * (CGFloat(users.count)) - (CGFloat((users.count - 1) * 15)), y: 0, width: viewAppBar.frame.size.width - 150 - (35 * (CGFloat(users.count - 1)) - (CGFloat((users.count - 1) * 15))), height: 44))
+            else if (pictureImage != "" && pictureImage != nil) {
+                viewAppBar.addSubview(imageProfile)
+                imageProfile.setImage(name: pictureImage!)
+                imageProfile.contentMode = .scaleAspectFill
+            } else {
+                viewAppBar.addSubview(imageProfile)
+                imageProfile.image = UIImage(systemName: "person")
+                imageProfile.contentMode = .scaleAspectFit
+                imageProfile.backgroundColor = .lightGray
             }
-        }
-        viewAppBar.addSubview(titleNavigation)
-        if ((dataPerson["isOfficial"] == "1" && !isContactCenter) || (dataPerson["isOfficial"] == "1" && fPinContacCenter.isEmpty)) {
-            var name = dataPerson["name"]!!
-            if (isContactCenter) {
-                name = name + " " + "Contact Center".localized()
+            
+            var titleNavigation = UILabel(frame: CGRect(x: 35, y: 0, width: viewAppBar.frame.size.width - 150, height: 44))
+            if dataPerson["f_pin"]!! != "-999"  && !isContactCenter && (blocking == "-1" || blocking == "1") {
+                titleNavigation = UILabel(frame: CGRect(x: 35, y: 0, width: viewAppBar.frame.size.width - 250, height: 44))
+            } else if isContactCenter {
+                if users.count > 0 {
+                    titleNavigation = UILabel(frame: CGRect(x: 35 * (CGFloat(users.count)) - (CGFloat((users.count - 1) * 15)), y: 0, width: viewAppBar.frame.size.width - 150 - (35 * (CGFloat(users.count - 1)) - (CGFloat((users.count - 1) * 15))), height: 44))
+                }
             }
-            titleNavigation.set(image: UIImage(named: "ic_official_flag", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(.white), with: "  \(name)", size: 15, y: -4)
-        } else {
-            if !isContactCenter {
-                titleNavigation.text = dataPerson["name"] as? String
+            viewAppBar.addSubview(titleNavigation)
+            if ((dataPerson["isOfficial"] == "1" && !isContactCenter) || (dataPerson["isOfficial"] == "1" && fPinContacCenter.isEmpty)) {
+                var name = dataPerson["name"]!!
+                if (isContactCenter) {
+                    name = name + " " + "Contact Center".localized()
+                }
+                titleNavigation.set(image: UIImage(named: "ic_official_flag", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(.white), with: "  \(name)", size: 15, y: -4)
             } else {
-                if users.count == 1 {
-                    titleNavigation.text = users[0].fullName
+                if !isContactCenter {
+                    titleNavigation.text = dataPerson["name"] as? String
                 } else {
-                    var stringName = ""
-                    for user in users {
-                        if stringName.isEmpty {
-                            stringName = user.fullName
-                        } else {
-                            stringName += ", \(user.fullName)"
+                    if users.count == 1 {
+                        titleNavigation.text = users[0].fullName
+                    } else {
+                        var stringName = ""
+                        for user in users {
+                            if stringName.isEmpty {
+                                stringName = user.fullName
+                            } else {
+                                stringName += ", \(user.fullName)"
+                            }
                         }
+                        titleNavigation.text = stringName
                     }
-                    titleNavigation.text = stringName
                 }
             }
+            titleNavigation.textColor = .white
+            titleNavigation.font = UIFont.systemFont(ofSize: 12).bold
+            navigationItem.titleView = viewAppBar
+        } else {
+            searchBar = UISearchBar()
+            searchBar.autocapitalizationType = .none
+            searchBar.delegate = self
+            searchBar.searchTextField.tintColor = .mainColor
+            searchBar.barTintColor = .secondaryColor
+            searchBar.searchTextField.backgroundColor = .secondaryColor
+            searchBar.showsCancelButton = false
+            navigationItem.titleView = searchBar
+            self.definesPresentationContext = true
         }
-        titleNavigation.textColor = .white
-        titleNavigation.font = UIFont.systemFont(ofSize: 12).bold
         
-        navigationItem.titleView = viewAppBar
-        
-        if copySession || forwardSession || deleteSession {
+        if copySession || forwardSession || deleteSession || isSearching {
             navigationItem.hidesBackButton = true
             navigationController?.interactivePopGestureRecognizer?.isEnabled = false
         } else {
@@ -1748,11 +1802,15 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
     }
     
     @objc func dismissKeyboard() {
-        textFieldSend.resignFirstResponder() // dismiss keyoard
-        if (self.constraintBottomAttachment.constant != 0.0) {
-            constraintBottomAttachment.constant = 0.0
-            self.viewSticker.removeConstraints(self.viewSticker.constraints)
-            self.viewSticker.removeFromSuperview()
+        if isSearching {
+            searchBar.resignFirstResponder()
+        } else {
+            textFieldSend.resignFirstResponder() // dismiss keyoard
+            if (self.constraintBottomAttachment.constant != 0.0) {
+                constraintBottomAttachment.constant = 0.0
+                self.viewSticker.removeConstraints(self.viewSticker.constraints)
+                self.viewSticker.removeFromSuperview()
+            }
         }
     }
     
@@ -1825,6 +1883,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
             let duration: CGFloat = info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat
             
             self.constraintViewTextField.constant = 0
+            self.constraintBottomContainerMultpileSelectSession.constant = 0
             UIView.animate(withDuration: TimeInterval(duration), animations: {
                 self.view.layoutIfNeeded()
             })
@@ -1847,13 +1906,21 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
             
             if self.constraintViewTextField.constant != keyboardHeight - 60 {
                 self.constraintViewTextField.constant = keyboardHeight - 60
+                if isSearching {
+                    self.constraintViewTextField.constant = self.constraintViewTextField.constant + 60
+                    self.constraintBottomContainerMultpileSelectSession.constant = -keyboardHeight
+                }
                 UIView.animate(withDuration: TimeInterval(duration), animations: {
                     self.view.layoutIfNeeded()
                 })
-                if (self.currentIndexpath != nil) {
-                    self.tableChatView.scrollToRow(at: IndexPath(row: self.currentIndexpath!.row, section: self.currentIndexpath!.section), at: .none, animated: false)
-                } else {
+                if isSearching {
                     self.tableChatView.scrollToBottom()
+                } else {
+                    if (self.currentIndexpath != nil) {
+                        self.tableChatView.scrollToRow(at: IndexPath(row: self.currentIndexpath!.row, section: self.currentIndexpath!.section), at: .none, animated: false)
+                    } else {
+                        self.tableChatView.scrollToBottom()
+                    }
                 }
             }
         }
@@ -2864,17 +2931,25 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
             if self.removed {
                 return
             }
-            self.handleReply(indexPath: indexPath!)
+            if self.isSearching {
+                self.cancelAction()
+            }
+            DispatchQueue.main.asyncAfter(deadline: .now() + 0.35, execute: {
+                self.handleReply(indexPath: indexPath!)
+            })
         })
         let forward = UIAction(title: "Forward".localized(), image: UIImage(systemName: "arrowshape.turn.up.right.fill"), handler: {(_) in
             if self.removed {
                 return
             }
-            self.forwardSession = true
+            if self.isSearching {
+                self.cancelAction()
+            }
             if self.reffId != nil {
                 self.deleteReplyView()
             }
             DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
+                self.forwardSession = true
                 let cancelButton = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(self.cancelAction))
                 if self.dataPerson["f_pin"] != "-999" && !self.isContactCenter {
                     self.navigationItem.rightBarButtonItems = nil
@@ -2896,11 +2971,14 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
             if self.removed {
                 return
             }
-            self.copySession = true
+            if self.isSearching {
+                self.cancelAction()
+            }
             if self.reffId != nil {
                 self.deleteReplyView()
             }
             DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
+                self.copySession = true
                 let cancelButton = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(self.cancelAction))
                 if self.dataPerson["f_pin"] != "-999" && !self.isContactCenter {
                     self.navigationItem.rightBarButtonItems = nil
@@ -2931,11 +3009,14 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
             if self.removed {
                 return
             }
-            self.deleteSession = true
+            if self.isSearching {
+                self.cancelAction()
+            }
             if self.reffId != nil {
                 self.deleteReplyView()
             }
             DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
+                self.deleteSession = true
                 let cancelButton = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(self.cancelAction))
                 if self.dataPerson["f_pin"] != "-999" && !self.isContactCenter {
                     self.navigationItem.rightBarButtonItems = nil
@@ -2995,6 +3076,25 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
                 self.forwardSession = false
             } else if self.deleteSession {
                 self.deleteSession = false
+            } else if self.isSearching {
+                self.countMatchesSearch = 0
+                self.isSearching = false
+            }
+            if self.viewTextfield.isHidden {
+                self.viewTextfield.isHidden = false
+            }
+            if self.viewAttachment.isHidden {
+                self.viewAttachment.isHidden = false
+            }
+            if !self.isSearching {
+                self.constraintBottomTableViewWithTextfield.constant = self.constraintBottomTableViewWithTextfield.constant + 70
+                DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: {
+                    if (self.currentIndexpath != nil) {
+                        self.tableChatView.scrollToRow(at: IndexPath(row: self.currentIndexpath!.row, section: self.currentIndexpath!.section), at: .none, animated: true)
+                    } else {
+                        self.tableChatView.scrollToBottom()
+                    }
+                })
             }
             let data = self.dataMessages.filter({ $0["isSelected"] as! Bool == true })
             for i in 0..<data.count {
@@ -3016,13 +3116,17 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
     }
     
     private func addMultipleSelectSession() {
+        viewTextfield.isHidden = true
+        viewAttachment.isHidden = true
+        constraintBottomTableViewWithTextfield.constant = constraintBottomTableViewWithTextfield.constant - 70
         view.addSubview(containerMultpileSelectSession)
         containerMultpileSelectSession.translatesAutoresizingMaskIntoConstraints = false
+        constraintBottomContainerMultpileSelectSession = containerMultpileSelectSession.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0)
         NSLayoutConstraint.activate([
             containerMultpileSelectSession.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
             containerMultpileSelectSession.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
-            containerMultpileSelectSession.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
-            containerMultpileSelectSession.heightAnchor.constraint(equalToConstant: 120)
+            constraintBottomContainerMultpileSelectSession,
+            containerMultpileSelectSession.heightAnchor.constraint(equalToConstant: 50)
         ])
         containerMultpileSelectSession.backgroundColor = .white
         addSubviewMultipleSession()
@@ -3044,52 +3148,104 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
         container.layer.shadowColor = UIColor.black.cgColor
         container.backgroundColor = .secondaryColor
         
-        let title = UILabel()
-        container.addSubview(title)
-        title.translatesAutoresizingMaskIntoConstraints = false
-        NSLayoutConstraint.activate([
-            title.centerXAnchor.constraint(equalTo: container.centerXAnchor),
-            title.centerYAnchor.constraint(equalTo:container.centerYAnchor),
-        ])
-        let countSelected = dataMessages.filter({ $0["isSelected"] as! Bool == true }).count
-        title.text = "\(countSelected) " + "Selected".localized()
-        title.textColor = .mainColor
-        title.font = UIFont.systemFont(ofSize: 15.0).bold
-        
-        let button = UIImageView()
-        container.addSubview(button)
-        button.translatesAutoresizingMaskIntoConstraints = false
-        NSLayoutConstraint.activate([
-            button.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 15),
-            button.centerYAnchor.constraint(equalTo:container.centerYAnchor),
-            button.widthAnchor.constraint(equalToConstant: 30),
-            button.heightAnchor.constraint(equalToConstant: 30),
-        ])
-        if copySession {
-            button.image = UIImage(systemName: "doc.on.doc")
-            if countSelected == 0 {
-                button.tintColor = .gray
-            } else {
-                button.tintColor = .mainColor
-            }
-        } else if forwardSession {
-            button.image = UIImage(systemName: "arrowshape.turn.up.right")
-            if countSelected == 0 {
-                button.tintColor = .gray
-            } else {
-                button.tintColor = .mainColor
-            }
-        } else if deleteSession {
-            button.image = UIImage(systemName: "trash")
-            if countSelected == 0 {
-                button.tintColor = .gray
-            } else {
-                button.tintColor = .red
+        if !isSearching {
+            let title = UILabel()
+            container.addSubview(title)
+            title.translatesAutoresizingMaskIntoConstraints = false
+            NSLayoutConstraint.activate([
+                title.centerXAnchor.constraint(equalTo: container.centerXAnchor),
+                title.centerYAnchor.constraint(equalTo:container.centerYAnchor),
+            ])
+            let countSelected = dataMessages.filter({ $0["isSelected"] as! Bool == true }).count
+            title.text = "\(countSelected) " + "Selected".localized()
+            title.textColor = .mainColor
+            title.font = UIFont.systemFont(ofSize: 15.0).bold
+            
+            let button = UIImageView()
+            container.addSubview(button)
+            button.translatesAutoresizingMaskIntoConstraints = false
+            NSLayoutConstraint.activate([
+                button.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 15),
+                button.centerYAnchor.constraint(equalTo:container.centerYAnchor),
+                button.widthAnchor.constraint(equalToConstant: 30),
+                button.heightAnchor.constraint(equalToConstant: 30),
+            ])
+            if copySession {
+                button.image = UIImage(systemName: "doc.on.doc")
+                if countSelected == 0 {
+                    button.tintColor = .gray
+                } else {
+                    button.tintColor = .mainColor
+                }
+            } else if forwardSession {
+                button.image = UIImage(systemName: "arrowshape.turn.up.right")
+                if countSelected == 0 {
+                    button.tintColor = .gray
+                } else {
+                    button.tintColor = .mainColor
+                }
+            } else if deleteSession {
+                button.image = UIImage(systemName: "trash")
+                if countSelected == 0 {
+                    button.tintColor = .gray
+                } else {
+                    button.tintColor = .red
+                }
             }
+            let buttonGesture = UITapGestureRecognizer(target: self, action: #selector(sessionAction))
+            button.isUserInteractionEnabled = true
+            button.addGestureRecognizer(buttonGesture)
+        } else {
+            buttonUp = UIButton()
+            container.addSubview(buttonUp)
+            buttonUp.translatesAutoresizingMaskIntoConstraints = false
+            NSLayoutConstraint.activate([
+                buttonUp.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 10),
+                buttonUp.centerYAnchor.constraint(equalTo:container.centerYAnchor),
+                buttonUp.widthAnchor.constraint(equalToConstant: 30),
+                buttonUp.heightAnchor.constraint(equalToConstant: 30),
+            ])
+            buttonUp.addTarget(self, action: #selector(upSearchText), for: .touchUpInside)
+            
+            buttonDown = UIButton()
+            container.addSubview(buttonDown)
+            buttonDown.translatesAutoresizingMaskIntoConstraints = false
+            NSLayoutConstraint.activate([
+                buttonDown.leadingAnchor.constraint(equalTo: buttonUp.trailingAnchor, constant: 15),
+                buttonDown.centerYAnchor.constraint(equalTo:container.centerYAnchor),
+                buttonDown.widthAnchor.constraint(equalToConstant: 30),
+                buttonDown.heightAnchor.constraint(equalToConstant: 30),
+            ])
+            buttonDown.addTarget(self, action: #selector(downSearchText), for: .touchUpInside)
+            
+            buttonUp.setImage(UIImage(systemName: "chevron.up"), for: .normal)
+            buttonUp.tintColor = .gray
+            
+            buttonDown.setImage(UIImage(systemName: "chevron.down"), for: .normal)
+            buttonDown.tintColor = .gray
+            
+            titleSearchMatches = UILabel()
+            container.addSubview(titleSearchMatches)
+            titleSearchMatches.translatesAutoresizingMaskIntoConstraints = false
+            NSLayoutConstraint.activate([
+                titleSearchMatches.centerXAnchor.constraint(equalTo: container.centerXAnchor),
+                titleSearchMatches.centerYAnchor.constraint(equalTo:container.centerYAnchor),
+            ])
+            titleSearchMatches.textColor = .mainColor
+            titleSearchMatches.font = UIFont.systemFont(ofSize: 15.0).bold
+            titleSearchMatches.isHidden = true
+            DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: {
+                self.searchBar.becomeFirstResponder()
+            })
         }
-        let buttonGesture = UITapGestureRecognizer(target: self, action: #selector(sessionAction))
-        button.isUserInteractionEnabled = true
-        button.addGestureRecognizer(buttonGesture)
+    }
+    
+    @objc func upSearchText() {
+        scrollToFirstSearchMessage(indexScroll: lastScrollIdxSearch + 1)
+    }
+    
+    @objc func downSearchText() {
+        scrollToFirstSearchMessage(indexScroll: lastScrollIdxSearch - 1)
     }
     
     @objc func sessionAction() {
@@ -4133,6 +4289,13 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
             }
         }
         
+        if isSearching && textSearch.count > 1 {
+            messageText.attributedText = textChat.richText(isSearching: true, textSearch: textSearch)
+            if textChat.lowercased().contains(textSearch) {
+                countMatchesSearch += 1
+            }
+        }
+        
         let stringDate = (dataMessages[indexPath.row]["server_date"] as? String) ?? ""
         if !stringDate.isEmpty {
             if (dataMessages[indexPath.row]["credential"] as? String) == "1" && dataMessages[indexPath.row]["lock"] as? String != "2" {
@@ -5035,6 +5198,83 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
             imageSticker.heightAnchor.constraint(equalToConstant: 30).isActive = true
         }
     }
+    
+    func scrollToFirstSearchMessage(indexScroll: Int = 1) {
+        if textSearch.count < 2 {
+            return
+        }
+        var lastIndex = 0
+        let messageTextForSearch: [[String: Any?]] = self.dataMessages.reversed()
+        for idx in 0..<messageTextForSearch.count {
+            if (messageTextForSearch[idx]["message_text"] as! String).lowercased().contains(textSearch) {
+                lastIndex += 1
+                if lastIndex < indexScroll {
+                    continue
+                }
+                lastScrollIdxSearch = lastIndex
+                let section = self.dataDates.firstIndex(of: messageTextForSearch[idx]["chat_date"] as! String)
+                if section == nil {
+                    return
+                }
+                let row = self.dataMessages.filter({ $0["chat_date"] as! String == self.dataDates[section!]}).firstIndex(where: { $0["message_id"] as? String == messageTextForSearch[idx]["message_id"] as? String})
+                if row == nil {
+                    return
+                }
+                let indexPath = IndexPath(row: row!, section: section!)
+                self.tableChatView.scrollToRow(at: indexPath, at: .middle, animated: true)
+                DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
+                    if let cell = self.tableChatView.cellForRow(at: indexPath) {
+                        let containerMessage = cell.contentView.subviews[0]
+                        let idMe = UserDefaults.standard.string(forKey: "me") as String?
+                        if (messageTextForSearch[idx]["f_pin"] as? String == idMe) {
+                            containerMessage.backgroundColor = .blueBubbleColor.withAlphaComponent(0.3)
+                            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+                                if (messageTextForSearch[idx]["attachment_flag"] as? String == "11") {
+                                    containerMessage.backgroundColor = .clear
+                                } else {
+                                    containerMessage.backgroundColor = .blueBubbleColor
+                                }
+                            }
+                        } else {
+                            containerMessage.backgroundColor = .grayColor.withAlphaComponent(0.3)
+                            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+                                if (messageTextForSearch[idx]["attachment_flag"] as? String == "11") {
+                                    containerMessage.backgroundColor = .clear
+                                } else {
+                                    containerMessage.backgroundColor = .grayColor
+                                }
+                            }
+                        }
+                    }
+                }
+                titleSearchMatches.isHidden = false
+                if countMatchesSearch != 0 {
+                    if countMatchesSearch > 1 {
+                        titleSearchMatches.text = "\(lastScrollIdxSearch) " + "of".localized() + " \(countMatchesSearch) " + "matches".localized()
+                    } else {
+                        titleSearchMatches.text = "\(countMatchesSearch) " + "matches".localized()
+                    }
+                } else {
+                    titleSearchMatches.text = "Not found".localized()
+                }
+                if lastScrollIdxSearch == countMatchesSearch || countMatchesSearch == 0 {
+                    buttonUp.isEnabled = false
+                    buttonUp.tintColor = .gray
+                } else {
+                    buttonUp.isEnabled = true
+                    buttonUp.tintColor = .mainColor
+                }
+                if countMatchesSearch == 0 || lastScrollIdxSearch == 1 || countMatchesSearch == 1 {
+                    buttonDown.isEnabled = false
+                    buttonDown.tintColor = .gray
+                } else {
+                    buttonDown.isEnabled = true
+                    buttonDown.tintColor = .mainColor
+                }
+                break
+            }
+        }
+    }
 }
 
 extension UITableView {
@@ -5079,6 +5319,18 @@ extension UIImage {
     }
 }
 
+extension EditorPersonal: UISearchBarDelegate {
+    
+    public func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
+        textSearch = searchText
+        countMatchesSearch = 0
+        titleSearchMatches.isHidden = true
+        tableChatView.reloadData()
+        scrollToFirstSearchMessage()
+    }
+}
+
+
 public class ObjectGesture: UITapGestureRecognizer {
     public var message_id = ""
     public var image_id = ""