alqindiirsyam 1 éve
szülő
commit
e66e7a7e4a

+ 10 - 1
appbuilder-ios/AppBuilder/AppBuilder/SecondTabViewController.swift

@@ -131,6 +131,7 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
         NotificationCenter.default.addObserver(self, selector: #selector(onReload(notification:)), name: NSNotification.Name(rawValue: "onMember"), object: nil)
         NotificationCenter.default.addObserver(self, selector: #selector(onReload(notification:)), name: NSNotification.Name(rawValue: "onUpdatePersonInfo"), object: nil)
         NotificationCenter.default.addObserver(self, selector: #selector(onReloadTab(notification:)), name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil)
+        NotificationCenter.default.addObserver(self, selector: #selector(onReloadTab(notification:)), name: NSNotification.Name(rawValue: "onTopic"), object: nil)
         
         
         
@@ -499,8 +500,16 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
         DispatchQueue.main.async { [self] in
             let data:[AnyHashable : Any] = notification.userInfo!
             if let dataMessage = data["message"] as? TMessage {
-                let indexChat = chats.firstIndex(where: { (dataMessage.getBody(key: CoreMessage_TMessageKey.MESSAGE_ID)).contains(",") ? $0.messageId == (dataMessage.getBody(key: CoreMessage_TMessageKey.MESSAGE_ID)).components(separatedBy: ",")[1]  : $0.messageId == dataMessage.getBody(key: CoreMessage_TMessageKey.MESSAGE_ID) })
+                var idMessage = dataMessage.getBody(key: CoreMessage_TMessageKey.MESSAGE_ID)
+                if idMessage.contains(",") {
+                    let listString = dataMessage.getBody(key: CoreMessage_TMessageKey.MESSAGE_ID).components(separatedBy: ",")
+                    idMessage = listString[listString.count - 1]
+                }
+                let indexChat = chats.firstIndex(where: { $0.messageId == idMessage })
                 if indexChat != nil {
+                    if dataMessage.getBody(key: CoreMessage_TMessageKey.DELETE_MESSAGE_FLAG) == "1" {
+                        chats[indexChat!].lock = "1"
+                    }
                     tableView.beginUpdates()
                     tableView.reloadRows(at: [IndexPath(row: indexChat!, section: 0)], with: .none)
                     tableView.endUpdates()

+ 10 - 0
appbuilder-ios/NexilisLite/NexilisLite/Source/CoreMessage_TMessageBank.swift

@@ -2476,4 +2476,14 @@ public class CoreMessage_TMessageBank {
         return tmessage
     }
     
+    public static func getUpdateLiveVideo(pTitle: String, pTagline: String) -> TMessage {
+        let tmessage = TMessage()
+        tmessage.mCode = CoreMessage_TMessageCode.UPDATE_LIVE_VIDEO
+        tmessage.mStatus = CoreMessage_TMessageUtil.getTID()
+        tmessage.mPIN = User.getMyPin() ?? ""
+        tmessage.mBodies[CoreMessage_TMessageKey.TITLE] = pTitle
+        tmessage.mBodies[CoreMessage_TMessageKey.TAGLINE] = pTagline
+        return tmessage
+    }
+    
 }

+ 10 - 2
appbuilder-ios/NexilisLite/NexilisLite/Source/IncomingThread.swift

@@ -60,7 +60,7 @@ class IncomingThread {
     }
     
     private func process(message: TMessage) {
-        //print("incoming process", message.toLogString())
+        print("incoming process", message.toLogString())
         if message.getCode() == CoreMessage_TMessageCode.LOGIN_FILE {
             loginFile(message: message)
         } else if message.getCode() == CoreMessage_TMessageCode.PUSH_MYSELF || message.getCode() == CoreMessage_TMessageCode.PUSH_MYSELF_ACK || message.getCode() == CoreMessage_TMessageCode.PULL_MYSELF {
@@ -189,7 +189,7 @@ class IncomingThread {
             notifyCalling(message: message)
         } else if message.getCode() == CoreMessage_TMessageCode.PAYMENT_NOTIFICATION {
             showTransactionApprovalRequest(message: message)
-        }  else if message.getCode() == CoreMessage_TMessageCode.LOGOUT {
+        } else if message.getCode() == CoreMessage_TMessageCode.LOGOUT {
             logoutDevice(message: message)
         } else {
             //print("unprocessed code", message.getCode())
@@ -204,6 +204,13 @@ class IncomingThread {
      *
      */
     
+    private func sendUpdateLiveStream(message: TMessage) -> Void {
+        var dataMessage: [AnyHashable : Any] = [:]
+        dataMessage["message"] = message
+        NotificationCenter.default.post(name: NSNotification.Name(rawValue: "liveStreamingUpdate"), object: nil, userInfo: dataMessage)
+        ack(message: message)
+    }
+    
     private func logoutDevice(message: TMessage) -> Void {
         if let packetId = message.mBodies[CoreMessage_TMessageKey.PACKET_ID] {
             _ = Nexilis.responseString(packetId: packetId, message: "00", timeout: 3000)
@@ -1796,6 +1803,7 @@ class IncomingThread {
         if let delegate = Nexilis.shared.streamingDelagate {
             delegate.onJoinLS(state: 999, message: title)
         }
+        sendUpdateLiveStream(message: message)
         ack(message: message)
     }
     private func changePersonInfoPassword(message: TMessage) -> Void {

+ 1 - 1
appbuilder-ios/NexilisLite/NexilisLite/Source/Model/Chat.swift

@@ -25,7 +25,7 @@ public class Chat: Model {
     public let official: String
     public let status: String
     public let credential: String
-    public let lock: String
+    public var lock: String
     
     public init(pin: String) {
         self.fpin = ""

+ 251 - 22
appbuilder-ios/NexilisLite/NexilisLite/Source/View/Chat/EditorGroup.swift

@@ -57,6 +57,7 @@ public class EditorGroup: UIViewController {
     let labelCounter = UILabel()
     let containerActionGroup = UIView()
     var removed = false
+    var isConfidential = false
     var isAck = false
     var copySession = false
     var forwardSession = false
@@ -95,6 +96,8 @@ public class EditorGroup: UIViewController {
     var groupImages: [String:[ImageGrouping]] = [:]
     var titleText: String!
     var lastY: CGFloat = 0
+    var listTimerCredential: [String: Int] = [:]
+    var timerCredential: [String: Timer] = [:]
     
     public override func viewDidDisappear(_ animated: Bool) {
         if self.isMovingFromParent {
@@ -414,6 +417,43 @@ public class EditorGroup: UIViewController {
                 })
             }
         })
+        for data in listTimerCredential {
+            if data.value > 0 {
+                var second = data.value
+                var timer = Timer()
+                timerCredential[data.key] = timer
+                timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: {_ in
+                    second -= 1
+                    self.listTimerCredential[data.key] = second
+                    let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == data.key })
+                    if (idx != nil) {
+                        let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as! String)
+                        let row = self.dataMessages.filter({ $0["chat_date"] as! String == self.dataMessages[idx!]["chat_date"] as! String}).firstIndex(where: { $0["message_id"] as? String == self.dataMessages[idx!]["message_id"] as? String })
+                        if second == 0 {
+                            timer.invalidate()
+                            self.listTimerCredential.removeValue(forKey: data.key)
+                            self.timerCredential.removeValue(forKey: data.key)
+                            UserDefaults.standard.removeObject(forKey: data.key)
+                            let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == data.key})
+                            if idx != nil {
+                                self.dataMessages[idx!]["lock"] = "2"
+                                self.dataMessages[idx!]["reff_id"] = ""
+                            }
+                            DispatchQueue.global().async {
+                                Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                                    _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
+                                        "lock" : "2"
+                                    ], _where: "message_id = '\(data.key)'")
+                                })
+                            }
+                        }
+                        if row != nil && section != nil  {
+                            self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
+                        }
+                    }
+                })
+            }
+        }
     }
     
     func getDataProfile(f_pin: String, message_id: String) -> [String: String]{
@@ -468,7 +508,7 @@ public class EditorGroup: UIViewController {
     
     private func getData() {
         Database.shared.database?.inTransaction({ (fmdb, rollback) in
-            var query = "SELECT message_id, f_pin, l_pin, message_scope_id, server_date, status, message_text, audio_id, video_id, image_id, thumb_id, read_receipts, chat_id, file_id, attachment_flag, reff_id, lock, is_stared, blog_id, is_call_center, call_center_id, opposite_pin FROM MESSAGE where chat_id='' AND l_pin='\(dataGroup["group_id"] as! String)' order by server_date asc"
+            var query = "SELECT message_id, f_pin, l_pin, message_scope_id, server_date, status, message_text, audio_id, video_id, image_id, thumb_id, read_receipts, chat_id, file_id, attachment_flag, reff_id, lock, is_stared, blog_id, credential, is_call_center, call_center_id, opposite_pin FROM MESSAGE where chat_id='' AND l_pin='\(dataGroup["group_id"] as! String)' order by server_date asc"
             if isHistoryCC {
                 query = "SELECT message_id, f_pin, l_pin, message_scope_id, server_date, status, message_text, audio_id, video_id, image_id, thumb_id, read_receipts, chat_id, file_id, attachment_flag, reff_id, lock, is_stared FROM MESSAGE where call_center_id='\(complaintId)' order by server_date asc"
             } else if (dataTopic["chat_id"] as! String != "") {
@@ -497,10 +537,55 @@ public class EditorGroup: UIViewController {
                     row["lock"] = cursorData.string(forColumnIndex: 16)
                     row["is_stared"] = cursorData.string(forColumnIndex: 17)
                     row["blog_id"] = cursorData.string(forColumnIndex: 18)
+                    row["credential"] = cursorData.string(forColumnIndex: 19)
                     row["isSelected"] = false
-                    row[TypeDataMessage.is_call_center] = cursorData.string(forColumnIndex: 19)
-                    row[TypeDataMessage.call_center_id] = cursorData.string(forColumnIndex: 20)
-                    row[TypeDataMessage.opposite_pin] = cursorData.string(forColumnIndex: 21)
+                    if row["credential"] as! String == "1" {
+                        let idMe = UserDefaults.standard.string(forKey: "me")!
+                        if row["f_pin"] as! String == idMe {
+                            let second = getSecondsDifferenceFromTwoDates(start: Date.init(milliseconds: Int64(row["server_date"] as! String)!), end: Date())
+                            if second > 60 {
+                                listTimerCredential[row["message_id"] as! String] = 0
+                                row["lock"] = "2"
+                                row["reff_id"] = ""
+                                DispatchQueue.global().async {
+                                    Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                                        _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
+                                            "lock" : "2"
+                                        ], _where: "message_id = '\(row["message_id"] as! String)'")
+                                    })
+                                }
+                            } else {
+                                let second = 60 - second
+                                listTimerCredential[row["message_id"] as! String] = second
+                            }
+                        } else {
+                            let hasMessageId = UserDefaults.standard.string(forKey: row["message_id"] as! String)
+                            if hasMessageId != nil {
+                                let second = getSecondsDifferenceFromTwoDates(start: Date.init(milliseconds: Int64(hasMessageId!)!), end: Date())
+                                if second > 60 {
+                                    listTimerCredential[row["message_id"] as! String] = 0
+                                    row["lock"] = "2"
+                                    row["reff_id"] = ""
+                                    DispatchQueue.global().async {
+                                        Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                                            _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
+                                                "lock" : "2"
+                                            ], _where: "message_id = '\(row["message_id"] as! String)'")
+                                        })
+                                    }
+                                } else {
+                                    let second = 60 - second
+                                    listTimerCredential[row["message_id"] as! String] = second
+                                }
+                            } else {
+                                UserDefaults.standard.set("\(Date().currentTimeMillis())", forKey: row["message_id"] as! String)
+                                listTimerCredential[row["message_id"] as! String] = 60
+                            }
+                        }
+                    }
+                    row[TypeDataMessage.is_call_center] = cursorData.string(forColumnIndex: 20)
+                    row[TypeDataMessage.call_center_id] = cursorData.string(forColumnIndex: 21)
+                    row[TypeDataMessage.opposite_pin] = cursorData.string(forColumnIndex: 22)
                     let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
                     let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
                     let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
@@ -841,6 +926,11 @@ public class EditorGroup: UIViewController {
                     } else {
                         row["file_id"] = ""
                     }
+                    if (chatData.keys.contains(CoreMessage_TMessageKey.CREDENTIAL)) {
+                        row["credential"] = chatData[CoreMessage_TMessageKey.CREDENTIAL]
+                    } else {
+                        row["credential"] = ""
+                    }
                     row["progress"] = 0.0
                     row["attachment_flag"] = chatData[CoreMessage_TMessageKey.ATTACHMENT_FLAG]
                     row["reff_id"] = chatData[CoreMessage_TMessageKey.REF_ID] ?? ""
@@ -853,9 +943,41 @@ public class EditorGroup: UIViewController {
                     }
                     row["chat_date"] = "Today".localized()
                     row["blog_id"] = chatData[CoreMessage_TMessageKey.BLOG_ID]
+                    if row["credential"] as! String == "1" {
+                        self.listTimerCredential[row["message_id"] as! String] = 60
+                    }
                     self.counter += 1
                     self.dataMessages.append(row)
                     self.tableChatView.insertRows(at: [IndexPath(row: self.dataMessages.filter({ $0["chat_date"] as! String == self.dataDates[self.dataDates.count - 1]}).count - 1, section: self.dataDates.count - 1)], with: .fade)
+                    if row["credential"] as! String == "1" {
+                        var timer = Timer()
+                        var minute = 60
+                        self.timerCredential[row["message_id"] as! String] = timer
+                        UserDefaults.standard.set("\(Date().currentTimeMillis())", forKey: row["message_id"] as! String)
+                        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: {_ in
+                            minute -= 1
+                            self.listTimerCredential[row["message_id"] as! String] = minute
+                            if minute == 0 {
+                                timer.invalidate()
+                                self.listTimerCredential.removeValue(forKey: row["message_id"] as! String)
+                                self.timerCredential.removeValue(forKey: row["message_id"] as! String)
+                                UserDefaults.standard.removeObject(forKey: row["message_id"] as! String)
+                                let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == row["message_id"] as? String})
+                                if idx != nil {
+                                    self.dataMessages[idx!]["lock"] = "2"
+                                    self.dataMessages[idx!]["reff_id"] = ""
+                                }
+                                DispatchQueue.global().async {
+                                    Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                                        _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
+                                            "lock" : "2"
+                                        ], _where: "message_id = '\(row["message_id"] as! String)'")
+                                    })
+                                }
+                            }
+                            self.tableChatView.reloadRows(at: [IndexPath(row: self.dataMessages.filter({ $0["chat_date"] as! String == self.dataDates[self.dataDates.count - 1]}).count - 1, section: self.dataDates.count - 1)], with: .none)
+                        })
+                    }
                     if  self.currentIndexpath?.row == (self.dataMessages.count - 2) {
                         if (self.viewIfLoaded?.window != nil) {
                             self.sendReadMessageStatus(chat_id: self.dataTopic["chat_id"] as! String, f_pin: chatData[CoreMessage_TMessageKey.F_PIN]!, message_scope_id: chatData[CoreMessage_TMessageKey.MESSAGE_SCOPE_ID]!, message_id: chatData[CoreMessage_TMessageKey.MESSAGE_ID]!)
@@ -1005,7 +1127,7 @@ public class EditorGroup: UIViewController {
     
     private func updateStatusDelete(idx: Int?, chatData: [String: String]) {
         do {
-            if self.dataMessages[idx!]["lock"] as! String == "1" {
+            if self.dataMessages[idx!]["lock"] != nil && self.dataMessages[idx!]["lock"] as! String == "1" {
                 return
             }
             self.dataMessages[idx!]["lock"] = "1"
@@ -1015,6 +1137,12 @@ public class EditorGroup: UIViewController {
             if row != nil && section != nil  {
                 self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
             }
+            if self.listTimerCredential[self.dataMessages[idx!]["message_id"] as! String] != nil {
+                self.listTimerCredential.removeValue(forKey: self.dataMessages[idx!]["message_id"] as! String)
+                self.timerCredential[self.dataMessages[idx!]["message_id"] as! String]?.invalidate()
+                self.timerCredential.removeValue(forKey: self.dataMessages[idx!]["message_id"] as! String)
+                UserDefaults.standard.removeObject(forKey: self.dataMessages[idx!]["message_id"] as! String)
+            }
             if self.reffId != nil && self.reffId == chatData["message_id"]! {
                 self.deleteReplyView()
             }
@@ -1371,16 +1499,32 @@ public class EditorGroup: UIViewController {
     
     @objc func showChooserACKConfidential() {
         let alertController = LibAlertController(title: "Message Mode".localized(), message: "Select".localized() + " " + "Message Mode".localized(), preferredStyle: .actionSheet)
+        let imageConfidential = resizeImage(image: UIImage(named: "confidential_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
         let imageAck = resizeImage(image: UIImage(named: "ack_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
+        let confidentialAction = UIAlertAction(title: "Confidential Message".localized(), style: .default, handler: { (UIAlertAction) in
+            if !self.isConfidential {
+                self.isConfidential = true
+                self.buttonAckConfidential.setImage(imageConfidential, for: .normal)
+            }
+            if self.isAck {
+                self.isAck = false
+            }
+        })
         let ackAction = UIAlertAction(title: "Confirmation Message".localized(), style: .default, handler: { (UIAlertAction) in
             if !self.isAck {
                 self.isAck = true
                 self.buttonAckConfidential.setImage(imageAck, for: .normal)
             }
+            if self.isConfidential {
+                self.isConfidential = false
+            }
         })
+        confidentialAction.setValue(imageConfidential, forKey: "image")
         ackAction.setValue(imageAck, forKey: "image")
+        alertController.addAction(confidentialAction)
         alertController.addAction(ackAction)
         alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: { (UIAlertAction) in
+            self.isConfidential = false
             self.isAck = false
             self.buttonAckConfidential.setImage(UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.withTintColor(.white).withRenderingMode(.alwaysTemplate), for: .normal)
         }))
@@ -1388,10 +1532,14 @@ public class EditorGroup: UIViewController {
     }
     
     public func setAckConfidential(isAck: Bool, isConfidential: Bool) {
+        self.isConfidential = isConfidential
         self.isAck = isAck
+        let imageConfidential = resizeImage(image: UIImage(named: "confidential_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
         let imageAck = resizeImage(image: UIImage(named: "ack_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
         if isAck {
             buttonAckConfidential.setImage(imageAck, for: .normal)
+        } else if isConfidential {
+            buttonAckConfidential.setImage(imageConfidential, for: .normal)
         } else {
             self.buttonAckConfidential.setImage(UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.withTintColor(.white).withRenderingMode(.alwaysTemplate), for: .normal)
         }
@@ -1425,6 +1573,10 @@ public class EditorGroup: UIViewController {
         if (self.dataTopic["chat_id"] as! String != "") {
             opposite_pin = self.dataTopic["chat_id"] as! String
         }
+        var credential = credential
+        if isConfidential {
+            credential = "1"
+        }
         var read_receipts = read_receipts
         if isAck {
             read_receipts = "8"
@@ -1445,6 +1597,9 @@ public class EditorGroup: UIViewController {
         let message = CoreMessage_TMessageBank.sendMessage(l_pin: dataGroup["group_id"] as! String, message_scope_id: message_scope_id, status: status, message_text: message_text, credential: credential, attachment_flag: attachment_flag, ex_blog_id: ex_blog_id, message_large_text: message_large_text, ex_format: ex_format, image_id: image_id, audio_id: audio_id, video_id: video_id, file_id: file_id, thumb_id: thumb_id, reff_id: reff_id, read_receipts: read_receipts, chat_id: dataTopic["chat_id"] as! String, is_call_center: is_call_center, call_center_id: call_center_id, opposite_pin: opposite_pin)
         Nexilis.addQueueMessage(message: message)
         let messageId = String(message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID]!)
+        if credential == "1" {
+            self.listTimerCredential[messageId] = 60
+        }
         var row: [String: Any?] = [:]
         row["message_id"] = messageId
         row["f_pin"] = idMe
@@ -1457,6 +1612,7 @@ public class EditorGroup: UIViewController {
         row["video_id"] = video_id
         row["image_id"] = image_id
         row["thumb_id"] = thumb_id
+        row["credential"] = credential
         row["read_receipts"] = read_receipts
         row["chat_id"] = dataTopic["chat_id"]!!
         row["file_id"] = file_id
@@ -1476,6 +1632,33 @@ public class EditorGroup: UIViewController {
         row["chat_date"] = "Today".localized()
         dataMessages.append(row)
         tableChatView.insertRows(at: [IndexPath(row: dataMessages.filter({ $0["chat_date"] as! String == dataDates[dataDates.count - 1]}).count - 1, section: dataDates.count - 1)], with: .fade)
+        if credential == "1" {
+            var timer = Timer()
+            var minute = 60
+            self.timerCredential[messageId] = timer
+            timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: {_ in
+                minute -= 1
+                self.listTimerCredential[messageId] = minute
+                if minute == 0 {
+                    timer.invalidate()
+                    self.listTimerCredential.removeValue(forKey: messageId)
+                    self.timerCredential.removeValue(forKey: messageId)
+                    let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == messageId})
+                    if idx != nil {
+                        self.dataMessages[idx!]["lock"] = "2"
+                        self.dataMessages[idx!]["reff_id"] = ""
+                    }
+                    DispatchQueue.global().async {
+                        Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                            _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
+                                "lock" : "2"
+                            ], _where: "message_id = '\(messageId)'")
+                        })
+                    }
+                }
+                self.tableChatView.reloadRows(at: [IndexPath(row: self.dataMessages.filter({ $0["chat_date"] as! String == self.dataDates[self.dataDates.count - 1]}).count - 1, section: self.dataDates.count - 1)], with: .none)
+            })
+        }
         if textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines) != "Send message".localized() && textFieldSend.textColor != UIColor.lightGray && constraintViewTextField.constant == 0 {
             textFieldSend.text = "Send message".localized()
             textFieldSend.textColor = UIColor.lightGray
@@ -1810,6 +1993,7 @@ extension EditorGroup: ImageVideoPickerDelegate, PreviewAttachmentImageVideoDele
             previewImageVC.delegate = self
             previewImageVC.isGroup = true
             previewImageVC.isAck = self.isAck
+            previewImageVC.isConfidential = self.isConfidential
             self.present(previewImageVC, animated: true, completion: nil)
         }
     }
@@ -2244,6 +2428,8 @@ extension EditorGroup: UITextViewDelegate {
             previewImageVC.image = UIPasteboard.general.image
             previewImageVC.fromCopy = true
             previewImageVC.currentTextTextField = textFieldSend.text
+            previewImageVC.isAck = self.isAck
+            previewImageVC.isConfidential = self.isConfidential
             previewImageVC.modalPresentationStyle = .custom
             previewImageVC.delegate = self
             self.present(previewImageVC, animated: true, completion: nil)
@@ -2519,8 +2705,13 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
                 children.insert(info, at: children.count - 1)
             }
         }
-        else if !(dataMessages[indexPath!.row]["image_id"] as! String).isEmpty || !(dataMessages[indexPath!.row]["video_id"] as! String).isEmpty || !(dataMessages[indexPath!.row]["file_id"] as! String).isEmpty || dataMessages[indexPath!.row]["attachment_flag"] as! String == "11" {
-            children = [reply ,delete]
+        else if !(dataMessages[indexPath!.row]["image_id"] as! String).isEmpty || !(dataMessages[indexPath!.row]["video_id"] as! String).isEmpty || !(dataMessages[indexPath!.row]["file_id"] as! String).isEmpty {
+            children = [star, reply, forward ,delete]
+            if (dataMessages[indexPath!.row]["f_pin"] as! String) == idMe {
+                children.insert(info, at: children.count - 1)
+            }
+        } else if dataMessages[indexPath!.row]["attachment_flag"] as! String == "11" {
+            children = [reply, delete]
             if (dataMessages[indexPath!.row]["f_pin"] as! String) == idMe {
                 children.insert(info, at: children.count - 1)
             }
@@ -2803,9 +2994,9 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
             let idMe = UserDefaults.standard.string(forKey: "me") as String?
             let dataFilterFpin = dataMessages.filter({ $0["f_pin"] as? String != idMe})
             let dataFilterLock = dataMessages.filter({ $0["lock"] as? String == "1"})
-            let statusDataRead = dataMessages.filter({ Int($0["status"] as! String)! >= 4})
+//            let statusDataRead = dataMessages.filter({ Int($0["status"] as! String)! >= 4})
             let statusFailed = dataMessages.filter({ Int($0["status"] as! String)! == 0})
-            if dataFilterFpin.count == 0 && dataFilterLock.count == 0 && statusDataRead.count == 0 && statusFailed.count == 0 {
+            if dataFilterFpin.count == 0 && dataFilterLock.count == 0 && statusFailed.count == 0 {
                 if let action = self.actionDelete(for: "everyone", title: "Delete".localized() + " \(countSelected) " + "For Everyone".localized(), dataMessages: dataMessages) {
                     alertController.addAction(action)
                 }
@@ -2817,7 +3008,7 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
     
     private func deleteMessage(l_pin: String, message_id: String, scope: String, type: String, chat: String) {
         let tmessage = CoreMessage_TMessageBank.deleteMessage(l_pin: l_pin, messageId: message_id, scope: scope, type: type, chat: chat)
-        Nexilis.addQueueMessage(message: tmessage)
+        Nexilis.deleteQueueMessage(message: tmessage)
     }
     
     private func queryMessageReply(message_id: String) -> [String: Any?] {
@@ -2980,9 +3171,14 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
                         self.dataMessages[idx!]["reff_id"] = ""
                     }
                 }
-                NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
-                cancelAction()
+                if self.listTimerCredential[dataMessages[i]["message_id"] as! String] != nil {
+                    self.listTimerCredential.removeValue(forKey: dataMessages[i]["message_id"] as! String)
+                    self.timerCredential[dataMessages[i]["message_id"] as! String]?.invalidate()
+                    self.timerCredential.removeValue(forKey: dataMessages[i]["message_id"] as! String)
+                }
             }
+            NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
+            cancelAction()
         }
     }
     
@@ -3303,6 +3499,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
         let videoChat = dataMessages[indexPath.row]["video_id"] as! String
         let fileChat = dataMessages[indexPath.row]["file_id"] as! String
         let reffChat = dataMessages[indexPath.row]["reff_id"] as! String
+        let dataTimer = listTimerCredential[(dataMessages[indexPath.row]["message_id"] as! String)]
         
         cellMessage.contentView.addSubview(containerMessage)
         containerMessage.translatesAutoresizingMaskIntoConstraints = false
@@ -3310,7 +3507,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
         let timeMessage = UILabel()
         cellMessage.contentView.addSubview(timeMessage)
         timeMessage.translatesAutoresizingMaskIntoConstraints = false
-        if (dataMessages[indexPath.row]["read_receipts"] as? String) == "8" {
+        if (dataMessages[indexPath.row]["read_receipts"] as? String) == "8" || ((dataMessages[indexPath.row]["credential"] as? String) == "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
             timeMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -40).isActive = true
         } else {
             timeMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
@@ -3395,7 +3592,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
             
             containerMessage.topAnchor.constraint(equalTo: cellMessage.contentView.topAnchor, constant: 5).isActive = true
             containerMessage.leadingAnchor.constraint(greaterThanOrEqualTo: cellMessage.contentView.leadingAnchor, constant: 60).isActive = true
-            if (dataMessages[indexPath.row]["read_receipts"] as? String) == "8" {
+            if (dataMessages[indexPath.row]["read_receipts"] as? String) == "8" || ((dataMessages[indexPath.row]["credential"] as? String) == "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
                 containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -40).isActive = true
             } else {
                 containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
@@ -3518,7 +3715,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
             }
             
             containerMessage.leadingAnchor.constraint(equalTo: profileMessage.trailingAnchor, constant: 5).isActive = true
-            if (dataMessages[indexPath.row]["read_receipts"] as? String) == "8" {
+            if (dataMessages[indexPath.row]["read_receipts"] as? String) == "8" || ((dataMessages[indexPath.row]["credential"] as? String) == "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
                 containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -40).isActive = true
             } else {
                 containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
@@ -3600,6 +3797,23 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
             imageAckView.image = imageAck
         }
         
+        if (dataMessages[indexPath.row]["credential"] as? String) == "1" && (dataMessages[indexPath.row]["lock"] as? String) != "2" {
+            let imageCredentialView = UIImageView()
+            let imageCredential = UIImage(named: "confidential_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
+            imageCredentialView.image = imageCredential
+            cellMessage.contentView.addSubview(imageCredentialView)
+            imageCredentialView.translatesAutoresizingMaskIntoConstraints = false
+            imageCredentialView.widthAnchor.constraint(equalToConstant: 30).isActive = true
+            imageCredentialView.heightAnchor.constraint(equalToConstant: 30).isActive = true
+            if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
+                imageCredentialView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
+                imageCredentialView.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 30).isActive = true
+            } else {
+                imageCredentialView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
+                imageCredentialView.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -30).isActive = true
+            }
+        }
+        
         messageText.numberOfLines = 0
         messageText.lineBreakMode = .byWordWrapping
         containerMessage.addSubview(messageText)
@@ -3638,6 +3852,10 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
             }
         }
         
+        if dataMessages[indexPath.row]["lock"] as? String == "2" {
+            textChat = "🚫 _"+"Message has expired".localized()+"_"
+        }
+        
         let imageSticker = UIImageView()
         
         if let attachmentFlag = dataMessages[indexPath.row]["attachment_flag"], let attachmentFlag = attachmentFlag as? String {
@@ -3739,13 +3957,24 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
         }
         
         let stringDate = (dataMessages[indexPath.row]["server_date"] as! String)
-        let date = Date(milliseconds: Int64(stringDate)!)
-        let formatter = DateFormatter()
-        formatter.dateFormat = "HH:mm"
-        formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
-        timeMessage.text = formatter.string(from: date as Date)
-        timeMessage.font = UIFont.systemFont(ofSize: 10, weight: .medium)
-        timeMessage.textColor = .lightGray
+        if !stringDate.isEmpty {
+            if (dataMessages[indexPath.row]["credential"] as? String) == "1" && dataMessages[indexPath.row]["lock"] as? String != "2" {
+                if dataTimer! >= 10 {
+                    timeMessage.text = "00:\(dataTimer!)"
+                } else {
+                    timeMessage.text = "00:0\(dataTimer!)"
+                }
+                timeMessage.textColor = .systemRed
+            } else {
+                let date = Date(milliseconds: Int64(stringDate) ?? 100)
+                let formatter = DateFormatter()
+                formatter.dateFormat = "HH:mm"
+                formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
+                timeMessage.text = formatter.string(from: date as Date)
+                timeMessage.textColor = .lightGray
+            }
+            timeMessage.font = UIFont.systemFont(ofSize: 10, weight: .medium)
+        }
         
         let imageThumb = UIImageView()
         let containerViewFile = UIView()

+ 10 - 4
appbuilder-ios/NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift

@@ -1692,7 +1692,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
     
     private func updateStatusDelete(idx: Int?, chatData: [String: String]) {
         do {
-            if self.dataMessages[idx!]["lock"] as! String == "1" {
+            if self.dataMessages[idx!]["lock"] != nil && self.dataMessages[idx!]["lock"] as! String == "1" {
                 return
             }
             self.dataMessages[idx!]["lock"] = "1"
@@ -3667,11 +3667,16 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
                 children.insert(info, at: children.count - 1)
             }
         }
-        else if !(dataMessages[indexPath!.row]["image_id"] as! String).isEmpty || !(dataMessages[indexPath!.row]["video_id"] as! String).isEmpty || !(dataMessages[indexPath!.row]["file_id"] as! String).isEmpty || dataMessages[indexPath!.row]["attachment_flag"] as! String == "11" {
+        else if !(dataMessages[indexPath!.row]["image_id"] as! String).isEmpty || !(dataMessages[indexPath!.row]["video_id"] as! String).isEmpty || !(dataMessages[indexPath!.row]["file_id"] as! String).isEmpty {
             children = [star, reply, forward ,delete]
             if (dataMessages[indexPath!.row]["f_pin"] as! String) == idMe {
                 children.insert(info, at: children.count - 1)
             }
+        } else if dataMessages[indexPath!.row]["attachment_flag"] as! String == "11" {
+            children = [reply, delete]
+            if (dataMessages[indexPath!.row]["f_pin"] as! String) == idMe {
+                children.insert(info, at: children.count - 1)
+            }
         } else {
             if (dataMessages[indexPath!.row]["f_pin"] as! String) == idMe {
                 children.insert(info, at: children.count - 1)
@@ -3968,9 +3973,9 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
             let idMe = UserDefaults.standard.string(forKey: "me") as String?
             let dataFilterFpin = dataMessages.filter({ $0["l_pin"] as? String == idMe})
             let dataFilterLock = dataMessages.filter({ $0["lock"] as? String == "1" || $0["lock"] as? String == "2" })
-            let statusDataRead = dataMessages.filter({ Int($0["status"] as! String)! >= 4})
+//            let statusDataRead = dataMessages.filter({ Int($0["status"] as! String)! >= 4})
             let statusFailed = dataMessages.filter({ Int($0["status"] as! String)! == 0})
-            if dataFilterFpin.count == 0 && dataFilterLock.count == 0 && statusDataRead.count == 0 && statusFailed.count == 0 {
+            if dataFilterFpin.count == 0 && dataFilterLock.count == 0 && statusFailed.count == 0 {
                 if let action = self.actionDelete(for: "everyone", title: "Delete".localized() + " \(countSelected) " + "For Everyone".localized(), dataMessages: dataMessages) {
                     alertController.addAction(action)
                 }
@@ -4164,6 +4169,7 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
                     self.timerCredential.removeValue(forKey: dataMessages[i]["message_id"] as! String)
                 }
             }
+            NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
             cancelAction()
         }
     }

+ 16 - 22
appbuilder-ios/NexilisLite/NexilisLite/Source/View/Chat/PreviewAttachmentImageVideo.swift

@@ -131,40 +131,34 @@ class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITex
     
     @objc func showChooserACKConfidential() {
         let alertController = LibAlertController(title: "Message Mode".localized(), message: "Select".localized() + " " + "Message Mode".localized(), preferredStyle: .actionSheet)
-        if !self.isGroup {
-            let imageConfidential = resizeImage(image: UIImage(named: "confidential_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
-            let confidentialAction = UIAlertAction(title: "Confidential Message".localized(), style: .default, handler: { (UIAlertAction) in
-                if !self.isConfidential {
-                    self.isConfidential = true
-                    self.buttonAckConfidential.setImage(imageConfidential, for: .normal)
-                }
-                if self.isAck {
-                    self.isAck = false
-                }
-                self.setPreviousVariableMessageMode()
-            })
-            confidentialAction.setValue(imageConfidential, forKey: "image")
-            alertController.addAction(confidentialAction)
-        }
+        let imageConfidential = resizeImage(image: UIImage(named: "confidential_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
+        let confidentialAction = UIAlertAction(title: "Confidential Message".localized(), style: .default, handler: { (UIAlertAction) in
+            if !self.isConfidential {
+                self.isConfidential = true
+                self.buttonAckConfidential.setImage(imageConfidential, for: .normal)
+            }
+            if self.isAck {
+                self.isAck = false
+            }
+            self.setPreviousVariableMessageMode()
+        })
+        confidentialAction.setValue(imageConfidential, forKey: "image")
+        alertController.addAction(confidentialAction)
         let imageAck = resizeImage(image: UIImage(named: "ack_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
         let ackAction = UIAlertAction(title: "Confirmation Message".localized(), style: .default, handler: { (UIAlertAction) in
             if !self.isAck {
                 self.isAck = true
                 self.buttonAckConfidential.setImage(imageAck, for: .normal)
             }
-            if !self.isGroup {
-                if self.isConfidential {
-                    self.isConfidential = false
-                }
+            if self.isConfidential {
+                self.isConfidential = false
             }
             self.setPreviousVariableMessageMode()
         })
         ackAction.setValue(imageAck, forKey: "image")
         alertController.addAction(ackAction)
         alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: { (UIAlertAction) in
-            if !self.isGroup {
-                self.isConfidential = false
-            }
+            self.isConfidential = false
             self.isAck = false
             self.buttonAckConfidential.setImage(UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.withTintColor(.white).withRenderingMode(.alwaysTemplate), for: .normal)
             self.setPreviousVariableMessageMode()

+ 10 - 1
appbuilder-ios/NexilisLite/NexilisLite/Source/View/Control/ContactChatViewController.swift

@@ -185,6 +185,7 @@ class ContactChatViewController: UITableViewController {
         NotificationCenter.default.addObserver(self, selector: #selector(onReload(notification:)), name: NSNotification.Name(rawValue: "onMember"), object: nil)
         NotificationCenter.default.addObserver(self, selector: #selector(onReloadTab(notification:)), name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil)
         NotificationCenter.default.addObserver(self, selector: #selector(onReload(notification:)), name: NSNotification.Name(rawValue: "onUpdatePersonInfo"), object: nil)
+        NotificationCenter.default.addObserver(self, selector: #selector(onReloadTab(notification:)), name: NSNotification.Name(rawValue: "onTopic"), object: nil)
         
         tableView.tableHeaderView = segment
         tableView.tableFooterView = UIView()
@@ -320,8 +321,16 @@ class ContactChatViewController: UITableViewController {
         DispatchQueue.main.async { [self] in
             let data:[AnyHashable : Any] = notification.userInfo!
             if let dataMessage = data["message"] as? TMessage {
-                let indexChat = chats.firstIndex(where: { (dataMessage.getBody(key: CoreMessage_TMessageKey.MESSAGE_ID)).contains(",") ? $0.messageId == (dataMessage.getBody(key: CoreMessage_TMessageKey.MESSAGE_ID)).components(separatedBy: ",")[1]  : $0.messageId == dataMessage.getBody(key: CoreMessage_TMessageKey.MESSAGE_ID) })
+                var idMessage = dataMessage.getBody(key: CoreMessage_TMessageKey.MESSAGE_ID)
+                if idMessage.contains(",") {
+                    let listString = dataMessage.getBody(key: CoreMessage_TMessageKey.MESSAGE_ID).components(separatedBy: ",")
+                    idMessage = listString[listString.count - 1]
+                }
+                let indexChat = chats.firstIndex(where: { $0.messageId == idMessage })
                 if indexChat != nil {
+                    if dataMessage.getBody(key: CoreMessage_TMessageKey.DELETE_MESSAGE_FLAG) == "1" {
+                        chats[indexChat!].lock = "1"
+                    }
                     tableView.beginUpdates()
                     tableView.reloadRows(at: [IndexPath(row: indexChat!, section: 0)], with: .none)
                     tableView.endUpdates()

+ 1 - 1
appbuilder-ios/NexilisLite/NexilisLite/Source/View/Streaming/QmeraCreateStreamingViewController.swift

@@ -325,7 +325,7 @@ public class QmeraCreateStreamingViewController: UITableViewController {
                     return
                 }
                 
-//                Nexilis.saveMessageBot(textMessage: json, blog_id: data["blog"] as? String ?? "", attachment_type: "27")
+                Nexilis.saveMessageBot(textMessage: json, blog_id: data["blog"] as? String ?? "", attachment_type: "27")
                 NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
             }
             controller.data = UserDefaults.standard.string(forKey: "me")!

+ 106 - 8
appbuilder-ios/NexilisLite/NexilisLite/Source/View/Streaming/QmeraStreamingViewController.swift

@@ -7,6 +7,7 @@
 
 import UIKit
 import nuSDKService
+import NotificationBannerSwift
 
 class QmeraStreamingViewController: UIViewController {
     
@@ -152,6 +153,10 @@ class QmeraStreamingViewController: UIViewController {
         return count
     }()
     
+    var alert: UIAlertController?
+    var textFields = [UITextField]()
+    let statusView = UIView()
+    
     override func viewDidLoad() {
         super.viewDidLoad()
         
@@ -165,11 +170,28 @@ class QmeraStreamingViewController: UIViewController {
         buttonBack.addTarget(self, action: #selector(close(sender:)), for: .touchUpInside)
         navigationItem.leftBarButtonItem = UIBarButtonItem(customView: buttonBack)
         
-        let statusView = UIView()
         let taglineView = UIView()
+        
+        let size = status.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: status.frame.height))
+        let width = size.width
+        status.frame = CGRect(x: 0, y: 0, width: width + 10, height: 30)
+        statusView.frame = CGRect(x: 0, y: 0, width: width + 10, height: 30)
+        statusView.backgroundColor = .white.withAlphaComponent(0.2)
+        statusView.layer.cornerRadius = 8.0
+        statusView.layer.masksToBounds = true
+        statusView.addSubview(status)
+        let tapGestureStatus = UITapGestureRecognizer(target: self, action: #selector(changeTitleOrTag(_:)))
+        statusView.tag = 0
+        statusView.isUserInteractionEnabled = true
+        statusView.addGestureRecognizer(tapGestureStatus)
+        
+        let tapGestureTagline = UITapGestureRecognizer(target: self, action: #selector(changeTitleOrTag(_:)))
+        taglineView.tag = 1
+        taglineView.addGestureRecognizer(tapGestureTagline)
+        
         view.backgroundColor = .clear
         view.addSubview(imageView)
-        view.addSubview(statusView)
+        navigationItem.titleView = statusView
         view.addSubview(taglineView)
         view.addSubview(stack)
         view.addLayoutGuide(keyboardLayoutGuide)
@@ -184,12 +206,7 @@ class QmeraStreamingViewController: UIViewController {
         keyboardLayoutGuide.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor).isActive = true
         
         imageView.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor)
-        statusView.backgroundColor = .white.withAlphaComponent(0.2)
-        statusView.layer.cornerRadius = 8.0
-        statusView.layer.masksToBounds = true
-        statusView.addSubview(status)
-        status.anchor(left: statusView.leftAnchor, right: statusView.rightAnchor, paddingLeft: 10, paddingRight: 10, centerX: statusView.centerXAnchor, centerY: statusView.centerYAnchor)
-        statusView.anchor(top: view.topAnchor, left: view.leftAnchor, right: view.rightAnchor, paddingTop: 30, paddingLeft: 80, paddingRight: 80, centerX: view.centerXAnchor, height: 30, dynamicLeft: true, dynamicRight: true)
+        
         taglineView.backgroundColor = .clear
         taglineView.addSubview(tagline)
         tagline.anchor(left: taglineView.leftAnchor, right: taglineView.rightAnchor, paddingLeft: 20, paddingRight: 20, centerX: taglineView.centerXAnchor, centerY: taglineView.centerYAnchor)
@@ -220,6 +237,7 @@ class QmeraStreamingViewController: UIViewController {
         view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(hideKeyboard)))
         NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
         NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
+        NotificationCenter.default.addObserver(self, selector: #selector(onUpdateTitleOrTagline(notification:)), name: NSNotification.Name(rawValue: "liveStreamingUpdate"), object: nil)
         
         Nexilis.shared.streamingDelagate = self
         if isLive {
@@ -358,6 +376,30 @@ class QmeraStreamingViewController: UIViewController {
         keyboard(notification: notification, show: false)
     }
     
+    @objc func onUpdateTitleOrTagline(notification: Notification) {
+        DispatchQueue.main.async { [self] in
+            let dataNotification:[AnyHashable : Any] = notification.userInfo!
+            if let message = dataNotification["message"] as? TMessage {
+                let broadcaster = message.getPIN()
+                if broadcaster == data && User.getMyPin() != data {
+                    let titleRaw = message.getBody(key: CoreMessage_TMessageKey.TITLE)
+                    let tagline = message.getBody(key: CoreMessage_TMessageKey.TAGLINE, default_value: self.tagline.text!)
+                    if !titleRaw.isEmpty || !tagline.isEmpty{
+                        let titleWithChar = titleRaw.components(separatedBy: "~")[1]
+                        let title = titleWithChar.components(separatedBy: "9632")[0]
+                        
+                        self.status.text = title
+                        let size = self.status.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: self.status.frame.height))
+                        let width = size.width
+                        self.status.frame = CGRect(x: 0, y: 0, width: width + 10, height: 30)
+                        self.statusView.frame = CGRect(x: 0, y: 0, width: width + 10, height: 30)
+                        self.tagline.text = tagline
+                    }
+                }
+            }
+        }
+    }
+    
     private func keyboard(notification: Notification, show: Bool) {
         let keyboardFrameEnd = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue
         let animationDuration = (notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue
@@ -411,6 +453,58 @@ class QmeraStreamingViewController: UIViewController {
         _ = Nexilis.write(message: CoreMessage_TMessageBank.getSendLSChat(l_pin: data, message_text: text))
     }
     
+    @objc func changeTitleOrTag(_ sender: UITapGestureRecognizer) {
+        if let tappedView = sender.view {
+            let type = tappedView.tag
+            self.alert = LibAlertController(title: "Title".localized(), message: nil, preferredStyle: .alert)
+            if type == 1 {
+                self.alert = LibAlertController(title: "Tagline".localized(), message: nil, preferredStyle: .alert)
+            }
+            self.textFields.removeAll()
+            self.alert?.addTextField{ (texfield) in
+                texfield.text = type == 0 ? (self.status.text!) : (self.tagline.text!)
+                texfield.addPadding(.right(40))
+                self.textFields.append(texfield)
+            }
+            let submitAction = UIAlertAction(title: "Submit".localized(), style: .default, handler: { (action) -> Void in
+                let textField = self.alert?.textFields![0]
+                if textField!.text!.isEmpty {
+                    self.showToast(message: (type == 0 ? "Title".localized() : "Tagline".localized()) + " " + "can't be empty".localized(), controller: self)
+                    return
+                }
+                if textField!.text! == (type == 0 ? (self.streamingData["title"] as! String) : (self.streamingData["tagline"] as! String)) {
+                    return
+                }
+                if !CheckConnection.isConnectedToNetwork()  || API.nGetCLXConnState() == 0 {
+                    let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
+                    imageView.tintColor = .white
+                    let banner = FloatingNotificationBanner(title: "Check your connection".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
+                }
+                
+                if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.getUpdateLiveVideo(pTitle: type == 0 ? "\(self.frontCamera ? 1 : 0)~\(textField!.text!)9632" : "\(self.frontCamera ? 1 : 0)~\(self.status.text!)9632", pTagline: type == 1 ? textField!.text! : self.tagline.text!)) {
+                    if response.isOk() {
+                        if type == 0 {
+                            self.status.text = textField!.text!
+                            let size = self.status.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: self.status.frame.height))
+                            let width = size.width
+                            self.status.frame = CGRect(x: 0, y: 0, width: width + 10, height: 30)
+                            self.statusView.frame = CGRect(x: 0, y: 0, width: width + 10, height: 30)
+                        } else {
+                            self.tagline.text = textField!.text!
+                        }
+                        self.showToast(message: "Successfully changed".localized(), controller: self)
+                    }
+                }
+            })
+            self.alert?.addAction(submitAction)
+            self.alert?.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: nil))
+            
+            self.present(self.alert!, animated: true, completion: nil)
+        }
+    }
+    
 }
 
 extension QmeraStreamingViewController: UITextViewDelegate {
@@ -558,6 +652,10 @@ extension QmeraStreamingViewController: LiveStreamingDelegate {
         } else if state == Nexilis.STREAMING_SEMINAR_ENDED {
             DispatchQueue.main.async {
                 self.status.text = "Streaming ended".localized()
+                let size = self.status.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: self.status.frame.height))
+                let width = size.width
+                self.status.frame = CGRect(x: 0, y: 0, width: width + 10, height: 30)
+                self.statusView.frame = CGRect(x: 0, y: 0, width: width + 10, height: 30)
             }
             DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
                 self.navigationController?.dismiss(animated: true, completion: nil)