alqindiirsyam 3 سال پیش
والد
کامیت
4f24ca7443

+ 2 - 0
appbuilder-ios/NexilisLite/Nexilis.swift

@@ -2302,6 +2302,8 @@ extension Nexilis: MessageDelegate {
                     text = "Sent link Video Conference Room"
                 } else if (m.keys.contains("A149")) && (m["A149"]!) == "24" {
                     text = "Sent link Quiz"
+                } else if (m.keys.contains("A118")) && (m["A118"]!) == "1" {
+                    text = "Sent Confidential Message"
                 }
                 var nameUser: String?
                 var profile = ""

+ 10 - 6
appbuilder-ios/NexilisLite/NexilisLite/Source/Model/Chat.swift

@@ -24,6 +24,7 @@ public class Chat: Model {
     public let profile: String
     public let official: String
     public let status: String
+    public let credential: String
     
     public init(pin: String) {
         self.fpin = ""
@@ -41,9 +42,10 @@ public class Chat: Model {
         self.profile = ""
         self.official = ""
         self.status = ""
+        self.credential = ""
     }
     
-    public init(fpin:String, pin: String, messageId: String, counter: String, messageText: String, serverDate: String, image: String, video: String, file: String, attachmentFlag: String, messageScope: String, name: String, profile: String, official: String, status: String ) {
+    public init(fpin:String, pin: String, messageId: String, counter: String, messageText: String, serverDate: String, image: String, video: String, file: String, attachmentFlag: String, messageScope: String, name: String, profile: String, official: String, status: String, credential: String) {
         self.fpin = fpin
         self.pin = pin
         self.messageId = messageId
@@ -59,6 +61,7 @@ public class Chat: Model {
         self.profile = profile
         self.official = official
         self.status = status
+        self.credential = credential
     }
     
     public static func == (lhs: Chat, rhs: Chat) -> Bool {
@@ -74,13 +77,13 @@ public class Chat: Model {
         Database.shared.database?.inTransaction({ (fmdb, rollback) in
             do {
                 let query = """
-                            select m.f_pin, ms.l_pin, ms.message_id, ms.counter, m.message_text, m.server_date, m.image_id, m.video_id, m.file_id, m.attachment_flag, m.message_scope_id, b.first_name || ' ' || ifnull(b.last_name, '') name, b.image_id profile, b.official_account, m.status from MESSAGE_SUMMARY ms, MESSAGE m, BUDDY b where ms.message_id = m.message_id and ms.l_pin = b.f_pin \(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'")
+                            select m.f_pin, ms.l_pin, ms.message_id, ms.counter, m.message_text, m.server_date, m.image_id, m.video_id, m.file_id, m.attachment_flag, m.message_scope_id, b.first_name || ' ' || ifnull(b.last_name, '') name, b.image_id profile, b.official_account, m.status, m.credential from MESSAGE_SUMMARY ms, MESSAGE m, BUDDY b where ms.message_id = m.message_id and ms.l_pin = b.f_pin \(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'")
                             union
-                            select m.f_pin, ms.l_pin, ms.message_id, ms.counter, m.message_text, m.server_date, m.image_id, m.video_id, m.file_id, m.attachment_flag, m.message_scope_id, 'Bot' name, '' profile, '', m.status from MESSAGE_SUMMARY ms, MESSAGE m where ms.message_id = m.message_id and ms.l_pin = '-999' \(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'")
+                            select m.f_pin, ms.l_pin, ms.message_id, ms.counter, m.message_text, m.server_date, m.image_id, m.video_id, m.file_id, m.attachment_flag, m.message_scope_id, 'Bot' name, '' profile, '', m.status, m.credential from MESSAGE_SUMMARY ms, MESSAGE m where ms.message_id = m.message_id and ms.l_pin = '-999' \(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'")
                             union
-                            select m.f_pin, ms.l_pin, ms.message_id, ms.counter, m.message_text, m.server_date, m.image_id, m.video_id, m.file_id, m.attachment_flag, m.message_scope_id, b.f_name || ' (\("Lounge".localized()))', b.image_id profile, b.official, m.status from MESSAGE_SUMMARY ms, MESSAGE m, GROUPZ b where ms.message_id = m.message_id and ms.l_pin = b.group_id \(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'")
+                            select m.f_pin, ms.l_pin, ms.message_id, ms.counter, m.message_text, m.server_date, m.image_id, m.video_id, m.file_id, m.attachment_flag, m.message_scope_id, b.f_name || ' (\("Lounge".localized()))', b.image_id profile, b.official, m.status, m.credential from MESSAGE_SUMMARY ms, MESSAGE m, GROUPZ b where ms.message_id = m.message_id and ms.l_pin = b.group_id \(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'")
                             union
-                            select m.f_pin, ms.l_pin, ms.message_id, ms.counter, m.message_text, m.server_date, m.image_id, m.video_id, m.file_id, m.attachment_flag, m.message_scope_id, c.f_name || ' (' || b.title || ')', b.thumb profile, '', m.status from MESSAGE_SUMMARY ms, MESSAGE m, DISCUSSION_FORUM b, GROUPZ c where ms.message_id = m.message_id and ms.l_pin = b.chat_id and b.group_id = c.group_id \(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'")
+                            select m.f_pin, ms.l_pin, ms.message_id, ms.counter, m.message_text, m.server_date, m.image_id, m.video_id, m.file_id, m.attachment_flag, m.message_scope_id, c.f_name || ' (' || b.title || ')', b.thumb profile, '', m.status, m.credential from MESSAGE_SUMMARY ms, MESSAGE m, DISCUSSION_FORUM b, GROUPZ c where ms.message_id = m.message_id and ms.l_pin = b.chat_id and b.group_id = c.group_id \(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'")
                             order by 6 desc
                             """
                 if let cursorData = Database.shared.getRecords(fmdb: fmdb, query: query) {
@@ -99,7 +102,8 @@ public class Chat: Model {
                                         name: cursorData.string(forColumnIndex: 11) ?? "",
                                         profile: cursorData.string(forColumnIndex: 12) ?? "",
                                         official: cursorData.string(forColumnIndex: 13) ?? "",
-                                        status: cursorData.string(forColumnIndex: 14) ?? "")
+                                        status: cursorData.string(forColumnIndex: 14) ?? "",
+                                        credential: cursorData.string(forColumnIndex: 15) ?? "")
                         chats.append(chat)
                     }
                     cursorData.close()

+ 2 - 0
appbuilder-ios/NexilisLite/NexilisLite/Source/Nexilis.swift

@@ -2302,6 +2302,8 @@ extension Nexilis: MessageDelegate {
                     text = "Sent link Video Conference Room"
                 } else if (m.keys.contains("A149")) && (m["A149"]!) == "24" {
                     text = "Sent link Quiz"
+                } else if (m.keys.contains("A118")) && (m["A118"]!) == "1" {
+                    text = "Sent Confidential Message"
                 }
                 var nameUser: String?
                 var profile = ""

+ 8 - 6
appbuilder-ios/NexilisLite/NexilisLite/Source/Utils.swift

@@ -73,29 +73,31 @@ public final class Utils {
     }()
     
     public static func previewMessageText(chat: Chat) -> Any {
-        if chat.attachmentFlag == "27" {
-            return "📄 " + "Live Streaming".localized()
+        if chat.credential == "1" {
+            return "Confidential Message".localized().richText()
+        } else if chat.attachmentFlag == "27" {
+            return ("📄 " + "Live Streaming".localized()).richText()
         } else if !chat.image.isEmpty {
             if !chat.messageText.isEmpty {
                 return "📷 \(chat.messageText)".richText()
             } else {
-                return "📷 " + "Photo".localized()
+                return ("📷 " + "Photo".localized()).richText()
             }
         }
         else if !chat.video.isEmpty {
             if !chat.messageText.isEmpty {
                 return "📹 \(chat.messageText)".richText()
             } else {
-                return "📹 " + "Video".localized()
+                return ("📹 " + "Video".localized()).richText()
             }
         }
         else if !chat.file.isEmpty {
             if chat.messageScope == "18" {
-                return "📄 Form"
+                return ("📄 Form").richText()
             }
             return "📄 " + chat.messageText.components(separatedBy: "|")[0]
         } else if chat.attachmentFlag == "11" {
-            return "❤️ " + "Sticker".localized()
+            return ("❤️ " + "Sticker".localized()).richText()
         }
         else {
             return chat.messageText.richText()

+ 231 - 18
appbuilder-ios/NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift

@@ -75,9 +75,14 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
     var showToast30s = false
     var allowTyping = true
     var hapticSwipeLeft = false
+    var listTimerCredential: [String: Int] = [:]
+    var timerCredential: [String: Timer] = [:]
     
     public override func viewDidDisappear(_ animated: Bool) {
         if self.isMovingFromParent {
+            for timer in self.timerCredential.values {
+                timer.invalidate()
+            }
             self.timeoutCC.invalidate()
             UserDefaults.standard.removeObject(forKey: "inEditorPersonal")
             NotificationCenter.default.removeObserver(self)
@@ -506,6 +511,43 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
         UIView.animate(withDuration: 0.5, animations: {
             self.tableChatView.alpha = 1.0
         })
+        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)
+                        }
+                    }
+                })
+            }
+        }
     }
     
     private func chatbot() {
@@ -676,7 +718,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
     }
     
     private func getData() {
-        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 FROM MESSAGE where (f_pin='\(dataPerson["f_pin"]!!)' or l_pin='\(dataPerson["f_pin"]!!)') AND (message_scope_id = '3' OR message_scope_id = '18') AND is_call_center = 0 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 FROM MESSAGE where (f_pin='\(dataPerson["f_pin"]!!)' or l_pin='\(dataPerson["f_pin"]!!)') AND (message_scope_id = '3' OR message_scope_id = '18') AND is_call_center = 0 order by server_date asc"
         if isContactCenter {
             if complaintId.isEmpty {
                 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 (f_pin='\(dataPerson["f_pin"]!!)' or l_pin='\(dataPerson["f_pin"]!!)') AND message_scope_id = '5' AND broadcast_flag = 0 AND is_call_center = 1 order by server_date asc"
@@ -722,6 +764,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                     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)
                     if let cursorStatus = Database.shared.getRecords(fmdb: fmdb, query: "SELECT status FROM MESSAGE_STATUS WHERE message_id='\(row["message_id"] as! String)'") {
                         while cursorStatus.next() {
                             row["status"] = cursorStatus.string(forColumnIndex: 0)
@@ -750,6 +793,50 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                     }
                     row["chat_date"] = chatDate(stringDate: row["server_date"] as! String)
                     row["isSelected"] = false
+                    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
+                            }
+                        }
+                    }
                     dataMessages.append(row)
                 }
                 cursorData.close()
@@ -757,6 +844,14 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
         })
     }
     
+    func getSecondsDifferenceFromTwoDates(start: Date, end: Date) -> Int {
+           let diff = Int(end.timeIntervalSince1970 - start.timeIntervalSince1970)
+
+           let hours = diff / 3600
+           let seconds = (diff - hours * 3600)
+           return seconds
+       }
+    
     func chatDate(stringDate: String) -> String {
         let date = Date(milliseconds: Int64(stringDate)!)
         let calendar = Calendar.current
@@ -1024,7 +1119,6 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                     if chatData[CoreMessage_TMessageKey.F_PIN] == nil {
                         return
                     }
-                    print("CHAT DATA \(chatData)")
                     var row: [String: Any?] = [:]
                     row["message_id"] = chatData[CoreMessage_TMessageKey.MESSAGE_ID]
                     row["f_pin"] = chatData[CoreMessage_TMessageKey.F_PIN]
@@ -1058,6 +1152,11 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                     } else {
                         row["read_receipts"] = ""
                     }
+                    if (chatData.keys.contains(CoreMessage_TMessageKey.CREDENTIAL)) {
+                        row["credential"] = chatData[CoreMessage_TMessageKey.CREDENTIAL]
+                    } else {
+                        row["credential"] = ""
+                    }
                     row["chat_id"] = ""
                     if (chatData.keys.contains(CoreMessage_TMessageKey.FILE_ID)) {
                         row["file_id"] = chatData[CoreMessage_TMessageKey.FILE_ID]
@@ -1077,8 +1176,40 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                     row["chat_date"] = "Today".localized()
                     row["blog_id"] = chatData[CoreMessage_TMessageKey.BLOG_ID]
                     self.counter += 1
+                    if row["credential"] as! String == "1" {
+                        self.listTimerCredential[row["message_id"] as! String] = 60
+                    }
                     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: .none)
+                    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.isContactCenter {
                         let idMe = UserDefaults.standard.string(forKey: "me")!
                         let onGoingCC = UserDefaults.standard.string(forKey: "onGoingCC") ?? ""
@@ -1176,6 +1307,12 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                                     self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
                                     self.tableChatView.reloadData()
                                 }
+                                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[CoreMessage_TMessageKey.MESSAGE_ID]! {
                                     self.deleteReplyView()
                                 }
@@ -1201,6 +1338,12 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                                     self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
                                     self.tableChatView.reloadData()
                                 }
+                                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()
                                 }
@@ -1578,6 +1721,9 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
     
     @objc func didTapExit() {
         if complaintId.isEmpty {
+            for timer in self.timerCredential.values {
+                timer.invalidate()
+            }
             self.timeoutCC.invalidate()
             UserDefaults.standard.removeObject(forKey: "inEditorPersonal")
             NotificationCenter.default.removeObserver(self)
@@ -1744,6 +1890,9 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
         let message = CoreMessage_TMessageBank.sendMessage(l_pin: l_pin, 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: chat_id, 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
@@ -1757,6 +1906,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
         row["image_id"] = image_id
         row["thumb_id"] = thumb_id
         row["read_receipts"] = read_receipts
+        row["credential"] = credential
         row["chat_id"] = chat_id
         row["file_id"] = file_id
         row["attachment_flag"] = attachment_flag
@@ -1772,6 +1922,33 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
         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: .none)
+        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
@@ -2724,7 +2901,7 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
         let idMe = UserDefaults.standard.string(forKey: "me") as String?
         if isContactCenter {
             children = [reply, copy]
-        } else if (dataMessages[indexPath!.row]["lock"] != nil && dataMessages[indexPath!.row]["lock"] as! String == "1") || dataMessages[indexPath!.row]["message_scope_id"] as! String == "18" || dataPerson["f_pin"] == "-999" {
+        } else if (dataMessages[indexPath!.row]["lock"] != nil && dataMessages[indexPath!.row]["lock"] as! String == "1") || dataMessages[indexPath!.row]["message_scope_id"] as! String == "18" || dataPerson["f_pin"] == "-999" || dataMessages[indexPath!.row]["credential"] as! String == "1" {
             children = [delete]
         } else if blocking == "1" || blocking == "-1" {
             children = [star, forward, copy ,delete]
@@ -2924,7 +3101,7 @@ 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"})
+            let dataFilterLock = dataMessages.filter({ $0["lock"] as? String == "1" || $0["lock"] as? String == "2" })
             if dataFilterFpin.count == 0 && dataFilterLock.count == 0 {
                 if let action = self.actionDelete(for: "everyone", title: "Delete".localized() + " \(countSelected) " + "For Everyone".localized(), dataMessages: dataMessages) {
                     alertController.addAction(action)
@@ -3069,8 +3246,13 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
                         self.dataMessages[idx!]["reff_id"] = ""
                     }
                 }
-                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)
+                }
             }
+            cancelAction()
         }
     }
     
@@ -3463,6 +3645,7 @@ extension EditorPersonal: 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)]
         
         cell.backgroundColor = .clear
         cell.selectionStyle = .none
@@ -3527,7 +3710,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
         let timeMessage = UILabel()
         cell.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: cell.contentView.bottomAnchor, constant: -40).isActive = true
         } else {
             timeMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -5).isActive = true
@@ -3584,7 +3767,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
         
         if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
             containerMessage.leadingAnchor.constraint(greaterThanOrEqualTo: cell.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: cell.contentView.bottomAnchor, constant: -40).isActive = true
             } else {
                 containerMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -5).isActive = true
@@ -3608,7 +3791,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
             
             timeMessage.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: -8).isActive = true
             
-            if dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as! String == "0" {
+            if (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as! String == "0") && (dataMessages[indexPath.row]["lock"] as? String != "2") {
                 cell.contentView.addSubview(statusMessage)
                 statusMessage.translatesAutoresizingMaskIntoConstraints = false
                 statusMessage.bottomAnchor.constraint(equalTo: timeMessage.topAnchor).isActive = true
@@ -3678,7 +3861,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
                     containerMessage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 15).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: cell.contentView.bottomAnchor, constant: -40).isActive = true
             } else {
                 containerMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -5).isActive = true
@@ -3739,6 +3922,23 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
             }
         }
         
+        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
+            cell.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
+            }
+        }
+        
         let messageText = UILabel()
         messageText.numberOfLines = 0
         messageText.lineBreakMode = .byWordWrapping
@@ -3794,6 +3994,10 @@ extension EditorPersonal: 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 {
             if attachmentFlag == "27" || attachmentFlag == "26" { // live streaming
@@ -3818,7 +4022,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
                     })
                 }
             }
-            else if attachmentFlag == "11" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as! String != "1") {
+            else if attachmentFlag == "11" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as! String != "1") && (dataMessages[indexPath.row]["lock"] as? String != "2") {
                 messageText.text = ""
                 topMarginText.constant = topMarginText.constant + 100
                 containerMessage.addSubview(imageSticker)
@@ -3866,19 +4070,28 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
         
         let stringDate = (dataMessages[indexPath.row]["server_date"] as? String) ?? ""
         if !stringDate.isEmpty {
-            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)
+            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)
-            timeMessage.textColor = .lightGray
         }
         
         let imageThumb = UIImageView()
         let containerViewFile = UIView()
         
-        if (!thumbChat.isEmpty && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as! String != "1")) {
+        if (!thumbChat.isEmpty && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as! String != "1") && (dataMessages[indexPath.row]["lock"] as? String != "2")) {
             topMarginText.constant = topMarginText.constant + 205
             
             containerMessage.addSubview(imageThumb)
@@ -3978,7 +4191,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
             }
         }
         
-        if (fileChat != "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as! String != "1") && dataMessages[indexPath.row]["message_scope_id"] as! String != "18") {
+        if (fileChat != "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as! String != "1") && dataMessages[indexPath.row]["message_scope_id"] as! String != "18" && (dataMessages[indexPath.row]["lock"] as? String != "2")) {
             topMarginText.constant = topMarginText.constant + 55
             
             let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory

+ 3 - 2
appbuilder-ios/NexilisLite/NexilisLite/Source/View/Chat/PreviewAttachmentImageVideo.swift

@@ -75,6 +75,7 @@ class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITex
         if (isHiddenTextField) {
             textFieldSend.removeFromSuperview()
             buttonSend.removeFromSuperview()
+            buttonAckConfidential.removeFromSuperview()
         } else {
             buttonSend.setImage(resizeImage(image: UIImage(named: "Send-(White)", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal), for: .normal)
             
@@ -275,9 +276,9 @@ class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITex
         if fromCopy || (imageVideoData![.mediaType] as! String == "public.image") {
             var originalImageName = ""
             if (fromCopy) {
-                originalImageName = "\(Date().millisecondsSince1970)_copyImage"
+                originalImageName = "\(Date().currentTimeMillis())_copyImage"
             } else if (imageVideoData![.imageURL] == nil) {
-                originalImageName = "\(Date().millisecondsSince1970)_takeImage"
+                originalImageName = "\(Date().currentTimeMillis())_takeImage"
             } else {
                 let urlImage = (imageVideoData![.imageURL] as! NSURL).absoluteString
                 originalImageName = (urlImage! as NSString).lastPathComponent