alqindiirsyam 3 年之前
父节点
当前提交
b1daf76345

+ 23 - 6
appbuilder-ios/AppBuilder/AppBuilder/SecondTabViewController.swift

@@ -806,11 +806,12 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
                 if data.fpin == idMe {
                     let stringMessage = NSMutableAttributedString(string: "")
                     let imageStatus = NSTextAttachment()
-                    if (data.status == "1" || data.status == "2" ) {
+                    let status = getRealStatus(messageId: data.messageId)
+                    if (status == "1" || status == "2" ) {
                         imageStatus.image = UIImage(named: "checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
-                    } else if (data.status == "3") {
+                    } else if (status == "3") {
                         imageStatus.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
-                    } else if (data.status == "8") {
+                    } else if (status == "8") {
                         imageStatus.image = UIImage(named: "message_status_ack", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
                     } else {
                         imageStatus.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.systemBlue)
@@ -828,11 +829,12 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
                 if data.fpin == idMe {
                     let stringMessage = NSMutableAttributedString(string: "")
                     let imageStatus = NSTextAttachment()
-                    if (data.status == "1" || data.status == "2" ) {
+                    let status = getRealStatus(messageId: data.messageId)
+                    if (status == "1" || status == "2" ) {
                         imageStatus.image = UIImage(named: "checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
-                    } else if (data.status == "3") {
+                    } else if (status == "3") {
                         imageStatus.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
-                    } else if (data.status == "8") {
+                    } else if (status == "8") {
                         imageStatus.image = UIImage(named: "message_status_ack", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
                     } else {
                         imageStatus.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.systemBlue)
@@ -947,6 +949,21 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
         return 70.0
     }
     
+    private func getRealStatus(messageId: String) -> String {
+        var status = "1"
+        Database.shared.database?.inTransaction({ (fmdb, rollback) in
+            if let cursorStatus = Database.shared.getRecords(fmdb: fmdb, query: "SELECT status, f_pin FROM MESSAGE_STATUS WHERE message_id='\(messageId)'") {
+                var listStatus: [Int] = []
+                while cursorStatus.next() {
+                    listStatus.append(Int(cursorStatus.string(forColumnIndex: 0)!)!)
+                }
+                cursorStatus.close()
+                status = "\(listStatus.min() ?? 2)"
+            }
+        })
+        return status
+    }
+    
 }
 
 

文件差异内容过多而无法显示
+ 540 - 299
appbuilder-ios/NexilisLite/Nexilis.swift


+ 3 - 0
appbuilder-ios/NexilisLite/NexilisLite/Source/Database.swift

@@ -301,6 +301,9 @@ public class Database {
                                 "'f_pin' text NOT NULL DEFAULT ''," +
                                 "'user_id' text NOT NULL DEFAULT ''," +
                                 "'last_update' integer," +
+                                "'time_delivered' integer," +
+                                "'time_read' integer," +
+                                "'time_ack' integer," +
                                 "'longitude' text NOT NULL DEFAULT ''," +
                                 "'latitude' text NOT NULL DEFAULT ''," +
                                 "'location' text NOT NULL DEFAULT '')", values: nil)

+ 39 - 5
appbuilder-ios/NexilisLite/NexilisLite/Source/Nexilis.swift

@@ -786,14 +786,48 @@ public class Nexilis: NSObject {
                     if t == "-1" || t == "-2" {
                         continue
                     }
+                    if status == "3" {
+                        _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE_STATUS", cvalues: [
+                                                            "status" : status,
+                                                            "time_delivered" : String(Date().currentTimeMillis()),
+                                                            "last_update" : String(Date().currentTimeMillis())], _where: "message_id = '\(t)' and f_pin = '\(l_pin)'")
+                    } else if status == "4" {
+                        _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE_STATUS", cvalues: [
+                                                            "status" : status,
+                                                            "time_read" : String(Date().currentTimeMillis()),
+                                                            "last_update" : String(Date().currentTimeMillis())], _where: "message_id = '\(t)' and f_pin = '\(l_pin)'")
+                    } else if status == "8" {
+                        _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE_STATUS", cvalues: [
+                                                            "status" : status,
+                                                            "time_ack" : String(Date().currentTimeMillis()),
+                                                            "last_update" : String(Date().currentTimeMillis())], _where: "message_id = '\(t)' and f_pin = '\(l_pin)'")
+                    } else {
+                        _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE_STATUS", cvalues: [
+                                                            "status" : status,
+                                                            "last_update" : String(Date().currentTimeMillis())], _where: "message_id = '\(t)' and f_pin = '\(l_pin)'")
+                    }
+                }
+            } else {
+                if status == "3" {
+                    _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE_STATUS", cvalues: [
+                                                        "status" : status,
+                                                        "time_delivered" : String(Date().currentTimeMillis()),
+                                                        "last_update" : String(Date().currentTimeMillis())], _where: "message_id = '\(message_id)' and f_pin = '\(l_pin)'")
+                } else if status == "4" {
                     _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE_STATUS", cvalues: [
                                                         "status" : status,
-                                                        "last_update" : String(Date().currentTimeMillis())], _where: "message_id = '\(t)' and f_pin = '\(l_pin)'")
+                                                        "time_read" : String(Date().currentTimeMillis()),
+                                                        "last_update" : String(Date().currentTimeMillis())], _where: "message_id = '\(message_id)' and f_pin = '\(l_pin)'")
+                } else if status == "8" {
+                    _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE_STATUS", cvalues: [
+                                                        "status" : status,
+                                                        "time_ack" : String(Date().currentTimeMillis()),
+                                                        "last_update" : String(Date().currentTimeMillis())], _where: "message_id = '\(message_id)' and f_pin = '\(l_pin)'")
+                } else {
+                    _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE_STATUS", cvalues: [
+                                                        "status" : status,
+                                                        "last_update" : String(Date().currentTimeMillis())], _where: "message_id = '\(message_id)' and f_pin = '\(l_pin)'")
                 }
-            } else {
-                _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE_STATUS", cvalues: [
-                                                    "status" : status,
-                                                    "last_update" : String(Date().currentTimeMillis())], _where: "message_id = '\(message_id)' and f_pin = '\(l_pin)'")
             }
         })
     }

+ 24 - 6
appbuilder-ios/NexilisLite/NexilisLite/Source/View/Chat/EditorGroup.swift

@@ -127,6 +127,8 @@ public class EditorGroup: UIViewController {
             }
         }
         
+        tableChatView.register(UITableViewCell.self, forCellReuseIdentifier: "cellEditorGroup")
+        
         loadData()
         setRightButtonItem()
         
@@ -401,7 +403,7 @@ public class EditorGroup: UIViewController {
                             }
                         }
                     }
-                    row["chat_date"] = chatDate(stringDate: row["server_date"] as! String, messageId: row["message_id"] as! String)
+                    row["chat_date"] = chatDate(stringDate: row["server_date"] as! String)
                     dataMessages.append(row)
                 }
 //                if isHistoryCC {
@@ -427,7 +429,7 @@ public class EditorGroup: UIViewController {
         return status
     }
     
-    private func chatDate(stringDate: String, messageId: String) -> String {
+    private func chatDate(stringDate: String) -> String {
         let date = Date(milliseconds: Int64(stringDate)!)
         let calendar = Calendar.current
         if (calendar.isDateInToday(date)) {
@@ -1122,7 +1124,7 @@ public class EditorGroup: UIViewController {
         row["f_pin"] = idMe
         row["l_pin"] = dataGroup["group_id"]!!
         row["message_scope_id"] = message_scope_id
-        row["server_date"] = "\(Date().millisecondsSince1970)"
+        row["server_date"] = "\(Date().currentTimeMillis())"
         row["status"] = status
         row["message_text"] = message_text
         row["audio_id"] = audio_id
@@ -1471,7 +1473,7 @@ extension EditorGroup: UIDocumentPickerDelegate, DocumentPickerDelegate, QLPrevi
             let urlFile = self.previewItem?.absoluteString
             var originaFileName = (urlFile! as NSString).lastPathComponent
             originaFileName = NSString(string: originaFileName).removingPercentEncoding!
-            let renamedNameFile = "Qmera_doc_" + "\(Date().millisecondsSince1970)_" + originaFileName
+            let renamedNameFile = "Qmera_doc_" + "\(Date().currentTimeMillis())_" + originaFileName
             let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
             let fileURL = documentsDirectory.appendingPathComponent(renamedNameFile)
             if !FileManager.default.fileExists(atPath: fileURL.path) {
@@ -1854,6 +1856,11 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
                 self.tableChatView.reloadData()
             }
         })
+        let info = UIAction(title: "Info".localized(), image: UIImage(systemName: "info.circle.fill"), handler: {(_) in
+            let messageInfoVC = MessageInfo()
+            messageInfoVC.data = dataMessages[indexPath!.row]
+            self.navigationController?.pushViewController(messageInfoVC, animated: true)
+        })
         let delete = UIAction(title: "Delete".localized(), image: UIImage(systemName: "trash.fill"), attributes: .destructive, handler: {(_) in
             self.deleteSession = true
             if self.reffId != nil {
@@ -1874,15 +1881,26 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
         
         var children: [UIMenuElement] = [star, reply, forward, copy, delete]
 //        let copyOption = self.copyOption(indexPath: indexPath!)
+        let idMe = UserDefaults.standard.string(forKey: "me") as String?
         
         if (dataMessages[indexPath!.row]["lock"] != nil && dataMessages[indexPath!.row]["lock"] as! String == "1") {
             children = [delete]
         }
         else if dataMessages[indexPath!.row]["f_pin"] as! String == "-999" {
             children = [star, reply ,delete]
+            if (dataMessages[indexPath!.row]["f_pin"] as! String) == idMe {
+                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 = [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]["f_pin"] as! String) == idMe {
+                children.insert(info, at: children.count - 1)
+            }
         }
         
         return UIContextMenuConfiguration(identifier: nil,
@@ -2395,7 +2413,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
         let idMe = UserDefaults.standard.string(forKey: "me") as String?
         let dataMessages = dataMessages.filter({$0["chat_date"] as! String == dataDates[indexPath.section]})
         
-        let cellMessage = UITableViewCell()
+        let cellMessage = tableView.dequeueReusableCell(withIdentifier: "cellEditorGroup", for: indexPath as IndexPath)
         cellMessage.backgroundColor = .clear
         cellMessage.selectionStyle = .none
         
@@ -3220,7 +3238,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
             if (self.dataTopic["chat_id"] as! String != "") {
                 opposite_pin = self.dataTopic["chat_id"] as! String
             }
-            let result = Nexilis.write(message: CoreMessage_TMessageBank.getAckLocationMessage(f_pin: dataMessages[indexPath.row]["f_pin"] as! String, message_id: dataMessages[indexPath.row]["message_id"] as! String, l_pin: opposite_pin, server_date: "\(Date().millisecondsSince1970)", message_scope_id: dataMessages[indexPath.row]["message_scope_id"] as! String, longitude: "", latitude: "", description: ""))
+            let result = Nexilis.write(message: CoreMessage_TMessageBank.getAckLocationMessage(f_pin: dataMessages[indexPath.row]["f_pin"] as! String, message_id: dataMessages[indexPath.row]["message_id"] as! String, l_pin: opposite_pin, server_date: "\(Date().currentTimeMillis())", message_scope_id: dataMessages[indexPath.row]["message_scope_id"] as! String, longitude: "", latitude: "", description: ""))
             if result != nil {
                 Database.shared.database?.inTransaction({ (fmdb, rollback) in
                     _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [

+ 60 - 44
appbuilder-ios/NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift

@@ -146,6 +146,8 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
             }
         }
         
+        tableChatView.register(UITableViewCell.self, forCellReuseIdentifier: "cellEditorPersonal")
+        
         loadData()
         setRightButtonItem()
         
@@ -746,7 +748,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                             }
                         }
                     }
-                    row["chat_date"] = chatDate(stringDate: row["server_date"] as! String, messageId: row["message_id"] as! String)
+                    row["chat_date"] = chatDate(stringDate: row["server_date"] as! String)
                     row["isSelected"] = false
                     dataMessages.append(row)
                 }
@@ -755,7 +757,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
         })
     }
     
-    func chatDate(stringDate: String, messageId: String) -> String {
+    func chatDate(stringDate: String) -> String {
         let date = Date(milliseconds: Int64(stringDate)!)
         let calendar = Calendar.current
         if (calendar.isDateInToday(date)) {
@@ -1747,7 +1749,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
         row["f_pin"] = idMe
         row["l_pin"] = dataPerson["f_pin"]!!
         row["message_scope_id"] = message_scope_id
-        row["server_date"] = "\(Date().millisecondsSince1970)"
+        row["server_date"] = "\(Date().currentTimeMillis())"
         row["status"] = status
         row["message_text"] = message_text
         row["audio_id"] = audio_id
@@ -2280,7 +2282,7 @@ extension EditorPersonal: UIDocumentPickerDelegate, DocumentPickerDelegate, QLPr
             let urlFile = self.previewItem?.absoluteString
             var originaFileName = (urlFile! as NSString).lastPathComponent
             originaFileName = NSString(string: originaFileName).removingPercentEncoding!
-            let renamedNameFile = "Qmera_doc_" + "\(Date().millisecondsSince1970)_" + originaFileName
+            let renamedNameFile = "Qmera_doc_" + "\(Date().currentTimeMillis())_" + originaFileName
             let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
             let fileURL = documentsDirectory.appendingPathComponent(renamedNameFile)
             if !FileManager.default.fileExists(atPath: fileURL.path) {
@@ -2687,6 +2689,11 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
                 self.tableChatView.reloadData()
             }
         })
+        let info = UIAction(title: "Info".localized(), image: UIImage(systemName: "info.circle.fill"), handler: {(_) in
+            let messageInfoVC = MessageInfo()
+            messageInfoVC.data = dataMessages[indexPath!.row]
+            self.navigationController?.pushViewController(messageInfoVC, animated: true)
+        })
         let delete = UIAction(title: "Delete".localized(), image: UIImage(systemName: "trash.fill"), attributes: .destructive, handler: {(_) in
             self.deleteSession = true
             if self.reffId != nil {
@@ -2713,7 +2720,7 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
         
         var children: [UIMenuElement] = [star, reply, forward, copy, delete]
 //        let copyOption = self.copyOption(indexPath: indexPath!)
-        
+        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" {
@@ -2723,9 +2730,19 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
             if !(dataMessages[indexPath!.row]["image_id"] as! String).isEmpty || !(dataMessages[indexPath!.row]["video_id"] as! String).isEmpty {
                 children = [star, forward ,delete]
             }
+            if (dataMessages[indexPath!.row]["f_pin"] as! String) == idMe {
+                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 = [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]["f_pin"] as! String) == idMe {
+                children.insert(info, at: children.count - 1)
+            }
         }
         
         return UIContextMenuConfiguration(identifier: nil,
@@ -3276,10 +3293,10 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
         let idMe = UserDefaults.standard.string(forKey: "me") as String?
         let dataMessages = dataMessages.filter({$0["chat_date"] as! String == dataDates[indexPath.section]})
         let profileMessage = UIImageView()
+        let cell = tableView.dequeueReusableCell(withIdentifier: "cellEditorPersonal", for: indexPath as IndexPath)
         
         if isContactCenter && isRequestContactCenter && dataMessages[indexPath.row]["category_cc"] != nil {
             
-            let cell = UITableViewCell()
             cell.backgroundColor = .clear
             cell.selectionStyle = .none
             
@@ -3445,23 +3462,22 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
         let fileChat = (dataMessages[indexPath.row]["file_id"] as? String) ?? ""
         let reffChat = (dataMessages[indexPath.row]["reff_id"] as? String) ?? ""
         
-        let cellMessage = UITableViewCell()
-        cellMessage.backgroundColor = .clear
-        cellMessage.selectionStyle = .none
+        cell.backgroundColor = .clear
+        cell.selectionStyle = .none
         let nameSender = UILabel()
         
         if isContactCenter {
             profileMessage.frame.size = CGSize(width: 35, height: 35)
-            cellMessage.contentView.addSubview(profileMessage)
+            cell.contentView.addSubview(profileMessage)
             profileMessage.translatesAutoresizingMaskIntoConstraints = false
-            profileMessage.topAnchor.constraint(equalTo: cellMessage.contentView.topAnchor, constant: 5).isActive = true
+            profileMessage.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 5).isActive = true
             if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
-                profileMessage.trailingAnchor.constraint(equalTo: cellMessage.contentView.trailingAnchor, constant: -15).isActive = true
+                profileMessage.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor, constant: -15).isActive = true
             } else {
                 if copySession || forwardSession || deleteSession {
-                    profileMessage.leadingAnchor.constraint(equalTo: cellMessage.contentView.leadingAnchor, constant: 50).isActive = true
+                    profileMessage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 50).isActive = true
                 } else {
-                    profileMessage.leadingAnchor.constraint(equalTo: cellMessage.contentView.leadingAnchor, constant: 15).isActive = true
+                    profileMessage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 15).isActive = true
                 }
             }
             profileMessage.heightAnchor.constraint(equalToConstant: 37).isActive = true
@@ -3478,12 +3494,12 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
             }
             profileMessage.contentMode = .scaleAspectFill
             
-            cellMessage.contentView.addSubview(nameSender)
+            cell.contentView.addSubview(nameSender)
             nameSender.translatesAutoresizingMaskIntoConstraints = false
             if markerCounter != nil && dataMessages[indexPath.row]["message_id"] as? String == markerCounter {
-                nameSender.topAnchor.constraint(equalTo: cellMessage.contentView.topAnchor, constant: 35).isActive = true
+                nameSender.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 35).isActive = true
             } else {
-                nameSender.topAnchor.constraint(equalTo: cellMessage.contentView.topAnchor, constant: 5).isActive = true
+                nameSender.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 5).isActive = true
             }
             nameSender.font = UIFont.systemFont(ofSize: 12, weight: UIFont.Weight(800))
             nameSender.text = user?.fullName ?? ""
@@ -3503,16 +3519,16 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
             containerMessage.addInteraction(interaction)
             containerMessage.isUserInteractionEnabled = true
         }
-        cellMessage.contentView.addSubview(containerMessage)
+        cell.contentView.addSubview(containerMessage)
         containerMessage.translatesAutoresizingMaskIntoConstraints = false
         
         let timeMessage = UILabel()
-        cellMessage.contentView.addSubview(timeMessage)
+        cell.contentView.addSubview(timeMessage)
         timeMessage.translatesAutoresizingMaskIntoConstraints = false
         if (dataMessages[indexPath.row]["read_receipts"] as? String) == "8" {
-            timeMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -40).isActive = true
+            timeMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -40).isActive = true
         } else {
-            timeMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
+            timeMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -5).isActive = true
         }
         
         let statusMessage = UIImageView()
@@ -3539,18 +3555,18 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
             }
             if showSelectedImage {
                 let selectedImage = UIImageView()
-                cellMessage.contentView.addSubview(selectedImage)
+                cell.contentView.addSubview(selectedImage)
                 selectedImage.translatesAutoresizingMaskIntoConstraints = false
                 selectedImage.frame.size = CGSize(width: 20, height: 20)
-                var leading = selectedImage.leadingAnchor.constraint(equalTo: cellMessage.contentView.leadingAnchor, constant: -20)
+                var leading = selectedImage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: -20)
                 selectedImage.isHidden = true
                 if copySession || forwardSession || deleteSession {
-                    leading = selectedImage.leadingAnchor.constraint(equalTo: cellMessage.contentView.leadingAnchor, constant: 15)
+                    leading = selectedImage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 15)
                     selectedImage.isHidden = false
                 }
                 NSLayoutConstraint.activate([
                     leading,
-                    selectedImage.centerYAnchor.constraint(equalTo: cellMessage.contentView.centerYAnchor),
+                    selectedImage.centerYAnchor.constraint(equalTo: cell.contentView.centerYAnchor),
                     selectedImage.widthAnchor.constraint(equalToConstant: 20),
                     selectedImage.heightAnchor.constraint(equalToConstant: 20)
                 ])
@@ -3565,18 +3581,18 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
         }
         
         if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
-            containerMessage.leadingAnchor.constraint(greaterThanOrEqualTo: cellMessage.contentView.leadingAnchor, constant: 60).isActive = true
+            containerMessage.leadingAnchor.constraint(greaterThanOrEqualTo: cell.contentView.leadingAnchor, constant: 60).isActive = true
             if (dataMessages[indexPath.row]["read_receipts"] as? String) == "8" {
-                containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -40).isActive = true
+                containerMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -40).isActive = true
             } else {
-                containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
+                containerMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -5).isActive = true
             }
             if isContactCenter {
                 containerMessage.topAnchor.constraint(equalTo: nameSender.bottomAnchor).isActive = true
                 containerMessage.trailingAnchor.constraint(equalTo: profileMessage.leadingAnchor, constant: -5).isActive = true
             } else {
-                containerMessage.topAnchor.constraint(equalTo: cellMessage.contentView.topAnchor, constant: 5).isActive = true
-                containerMessage.trailingAnchor.constraint(equalTo: cellMessage.contentView.trailingAnchor, constant: -15).isActive = true
+                containerMessage.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 5).isActive = true
+                containerMessage.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor, constant: -15).isActive = true
             }
             containerMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
             if (dataMessages[indexPath.row]["attachment_flag"] as? String == "11" && dataMessages[indexPath.row]["reff_id"]as? String == "") {
@@ -3591,7 +3607,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" {
-                cellMessage.contentView.addSubview(statusMessage)
+                cell.contentView.addSubview(statusMessage)
                 statusMessage.translatesAutoresizingMaskIntoConstraints = false
                 statusMessage.bottomAnchor.constraint(equalTo: timeMessage.topAnchor).isActive = true
                 statusMessage.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: -8).isActive = true
@@ -3613,16 +3629,16 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
                 if isContactCenter {
                     containerMessage.topAnchor.constraint(equalTo: nameSender.bottomAnchor).isActive = true
                 } else {
-                    containerMessage.topAnchor.constraint(equalTo: cellMessage.contentView.topAnchor, constant: 35).isActive = true
+                    containerMessage.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 35).isActive = true
                 }
                 
                 let newMessagesView = UIView()
-                cellMessage.contentView.addSubview(newMessagesView)
+                cell.contentView.addSubview(newMessagesView)
                 newMessagesView.translatesAutoresizingMaskIntoConstraints = false
                 NSLayoutConstraint.activate([
                     newMessagesView.topAnchor.constraint(equalTo: newMessagesView.topAnchor),
                     newMessagesView.bottomAnchor.constraint(equalTo: containerMessage.topAnchor),
-                    newMessagesView.centerXAnchor.constraint(equalTo: cellMessage.contentView.centerXAnchor),
+                    newMessagesView.centerXAnchor.constraint(equalTo: cell.contentView.centerXAnchor),
                     newMessagesView.heightAnchor.constraint(equalToConstant: 30),
                     newMessagesView.widthAnchor.constraint(greaterThanOrEqualToConstant: 60)
                 ])
@@ -3648,24 +3664,24 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
                 if isContactCenter {
                     containerMessage.topAnchor.constraint(equalTo: nameSender.bottomAnchor).isActive = true
                 } else {
-                    containerMessage.topAnchor.constraint(equalTo: cellMessage.contentView.topAnchor, constant: 5).isActive = true
+                    containerMessage.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 5).isActive = true
                 }
             }
             if isContactCenter {
                 containerMessage.leadingAnchor.constraint(equalTo: profileMessage.trailingAnchor, constant: 5).isActive = true
             } else {
                 if copySession || forwardSession || deleteSession {
-                    containerMessage.leadingAnchor.constraint(equalTo: cellMessage.contentView.leadingAnchor, constant: 50).isActive = true
+                    containerMessage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 50).isActive = true
                 } else {
-                    containerMessage.leadingAnchor.constraint(equalTo: cellMessage.contentView.leadingAnchor, constant: 15).isActive = true
+                    containerMessage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 15).isActive = true
                 }
             }
             if (dataMessages[indexPath.row]["read_receipts"] as? String) == "8" {
-                containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -40).isActive = true
+                containerMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -40).isActive = true
             } else {
-                containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
+                containerMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -5).isActive = true
             }
-            containerMessage.trailingAnchor.constraint(lessThanOrEqualTo: cellMessage.contentView.trailingAnchor, constant: -60).isActive = true
+            containerMessage.trailingAnchor.constraint(lessThanOrEqualTo: cell.contentView.trailingAnchor, constant: -60).isActive = true
             containerMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
             if dataMessages[indexPath.row]["attachment_flag"] as? String == "11" && dataMessages[indexPath.row]["reff_id"]as? String == "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as! String != "1") {
                 containerMessage.backgroundColor = .clear
@@ -3681,7 +3697,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
         
         if dataMessages[indexPath.row]["is_stared"] as? String == "1" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as! String == "0") {
             let imageStared = UIImageView()
-            cellMessage.contentView.addSubview(imageStared)
+            cell.contentView.addSubview(imageStared)
             imageStared.translatesAutoresizingMaskIntoConstraints = false
             if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
                 imageStared.bottomAnchor.constraint(equalTo: statusMessage.topAnchor).isActive = true
@@ -3704,7 +3720,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
                 imageAck = UIImage(named: "ack_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
             }
             imageAckView.image = imageAck
-            cellMessage.contentView.addSubview(imageAckView)
+            cell.contentView.addSubview(imageAckView)
             imageAckView.translatesAutoresizingMaskIntoConstraints = false
             imageAckView.widthAnchor.constraint(equalToConstant: 30).isActive = true
             imageAckView.heightAnchor.constraint(equalToConstant: 30).isActive = true
@@ -4231,7 +4247,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
 //        let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(panGestureCellAction))
 //        panGestureRecognizer.delegate = self
 //        cellMessage.addGestureRecognizer(panGestureRecognizer)
-        return cellMessage
+        return cell
     }
     
     @objc func tapAck(_ sender: ObjectGesture) {
@@ -4248,7 +4264,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
             return
         }
         DispatchQueue.global().async {
-            let result = Nexilis.write(message: CoreMessage_TMessageBank.getAckLocationMessage(f_pin: dataMessages[indexPath.row]["f_pin"] as! String, message_id: dataMessages[indexPath.row]["message_id"] as! String, l_pin: dataMessages[indexPath.row]["l_pin"] as! String, server_date: "\(Date().millisecondsSince1970)", message_scope_id: dataMessages[indexPath.row]["message_scope_id"] as! String, longitude: "", latitude: "", description: ""))
+            let result = Nexilis.write(message: CoreMessage_TMessageBank.getAckLocationMessage(f_pin: dataMessages[indexPath.row]["f_pin"] as! String, message_id: dataMessages[indexPath.row]["message_id"] as! String, l_pin: dataMessages[indexPath.row]["l_pin"] as! String, server_date: "\(Date().currentTimeMillis())", message_scope_id: dataMessages[indexPath.row]["message_scope_id"] as! String, longitude: "", latitude: "", description: ""))
             if result != nil {
                 Database.shared.database?.inTransaction({ (fmdb, rollback) in
                     _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [

+ 23 - 6
appbuilder-ios/NexilisLite/NexilisLite/Source/View/Control/ContactChatViewController.swift

@@ -710,11 +710,12 @@ extension ContactChatViewController {
                 if data.fpin == idMe {
                     let stringMessage = NSMutableAttributedString(string: "")
                     let imageStatus = NSTextAttachment()
-                    if (data.status == "1" || data.status == "2" ) {
+                    let status = getRealStatus(messageId: data.messageId)
+                    if (status == "1" || status == "2" ) {
                         imageStatus.image = UIImage(named: "checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
-                    } else if (data.status == "3") {
+                    } else if (status == "3") {
                         imageStatus.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
-                    } else if (data.status == "8") {
+                    } else if (status == "8") {
                         imageStatus.image = UIImage(named: "message_status_ack", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
                     } else {
                         imageStatus.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.systemBlue)
@@ -732,11 +733,12 @@ extension ContactChatViewController {
                 if data.fpin == idMe {
                     let stringMessage = NSMutableAttributedString(string: "")
                     let imageStatus = NSTextAttachment()
-                    if (data.status == "1" || data.status == "2" ) {
+                    let status = getRealStatus(messageId: data.messageId)
+                    if (status == "1" || status == "2" ) {
                         imageStatus.image = UIImage(named: "checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
-                    } else if (data.status == "3") {
+                    } else if (status == "3") {
                         imageStatus.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
-                    } else if (data.status == "8") {
+                    } else if (status == "8") {
                         imageStatus.image = UIImage(named: "message_status_ack", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
                     } else {
                         imageStatus.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.systemBlue)
@@ -876,6 +878,21 @@ extension ContactChatViewController {
         return 70
     }
     
+    private func getRealStatus(messageId: String) -> String {
+        var status = "1"
+        Database.shared.database?.inTransaction({ (fmdb, rollback) in
+            if let cursorStatus = Database.shared.getRecords(fmdb: fmdb, query: "SELECT status, f_pin FROM MESSAGE_STATUS WHERE message_id='\(messageId)'") {
+                var listStatus: [Int] = []
+                while cursorStatus.next() {
+                    listStatus.append(Int(cursorStatus.string(forColumnIndex: 0)!)!)
+                }
+                cursorStatus.close()
+                status = "\(listStatus.min() ?? 2)"
+            }
+        })
+        return status
+    }
+    
 }
 
 

+ 201 - 0
appbuilder-ios/NexilisLite/NexilisLite/Source/View/Control/MessageInfo.swift

@@ -0,0 +1,201 @@
+//
+//  MessageInfo.swift
+//  NexilisLite
+//
+//  Created by Qindi on 11/08/22.
+//
+
+import UIKit
+
+class MessageInfo: UIViewController, UITableViewDelegate, UITableViewDataSource {
+    var data: [String: Any?] = [:]
+    var dataStatus: [[String: Any?]] = []
+    private var tableStatus: UITableView!
+    var dateMessage = ""
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+
+        title = "Message Info".localized()
+        view.backgroundColor = .white
+        navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
+        navigationController?.navigationBar.topItem?.backButtonTitle = "Back".localized()
+        
+        tableStatus = UITableView(frame: .zero, style: .grouped)
+        tableStatus.register(UITableViewCell.self, forCellReuseIdentifier: "cellStatus")
+        tableStatus.dataSource = self
+        tableStatus.delegate = self
+        tableStatus.separatorStyle = .none
+        tableStatus.bounces = false
+        
+        getData()
+        dateMessage = chatDate(stringDate: data["server_date"] as! String)
+        
+        tableStatus.reloadData()
+        
+        view.addSubview(tableStatus)
+        tableStatus.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor)
+//        buildMessage()
+    }
+    
+    private func getData() {
+        Database.shared.database?.inTransaction({ (fmdb, rollback) in
+            if let cursorData = Database.shared.getRecords(fmdb: fmdb, query: "SELECT f_pin, status, time_delivered, time_read, time_ack FROM MESSAGE_STATUS where message_id='\(data["message_id"]!!)'") {
+                while cursorData.next() {
+                    var row: [String: Any?] = [:]
+                    row["f_pin"] = cursorData.string(forColumnIndex: 0)
+                    row["status"] = cursorData.string(forColumnIndex: 1)
+                    row["time_delivered"] = cursorData.string(forColumnIndex: 2)
+                    row["time_read"] = cursorData.string(forColumnIndex: 3)
+                    row["time_ack"] = cursorData.string(forColumnIndex: 4)
+                    dataStatus.append(row)
+                }
+                cursorData.close()
+            }
+        })
+    }
+    
+    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
+        if section == 0 {
+            let containerView = UIView()
+            containerView.backgroundColor = .clear
+            
+            let dateView = UIView()
+            containerView.addSubview(dateView)
+            dateView.translatesAutoresizingMaskIntoConstraints = false
+            var topAnchor = dateView.topAnchor.constraint(equalTo: containerView.topAnchor)
+            if section == 0 {
+                topAnchor = dateView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 10.0)
+            }
+            NSLayoutConstraint.activate([
+                topAnchor,
+                dateView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
+                dateView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
+                dateView.heightAnchor.constraint(equalToConstant: 30),
+                dateView.widthAnchor.constraint(greaterThanOrEqualToConstant: 60)
+            ])
+            dateView.backgroundColor = .orangeColor
+            dateView.layer.cornerRadius = 15.0
+            dateView.clipsToBounds = true
+            
+            let labelDate = UILabel()
+            dateView.addSubview(labelDate)
+            labelDate.translatesAutoresizingMaskIntoConstraints = false
+            NSLayoutConstraint.activate([
+                labelDate.centerYAnchor.constraint(equalTo: dateView.centerYAnchor),
+                labelDate.centerXAnchor.constraint(equalTo: dateView.centerXAnchor),
+                labelDate.leadingAnchor.constraint(equalTo: dateView.leadingAnchor, constant: 10),
+                labelDate.trailingAnchor.constraint(equalTo: dateView.trailingAnchor, constant: -10),
+            ])
+            labelDate.textAlignment = .center
+            labelDate.textColor = .secondaryColor
+            labelDate.font = UIFont.systemFont(ofSize: 12, weight: .medium)
+            labelDate.text = dateMessage
+            return containerView
+        }
+        return UIView()
+    }
+    
+    func numberOfSections(in tableView: UITableView) -> Int {
+        return 1
+    }
+    
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return 3
+    }
+    
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let cell = tableView.dequeueReusableCell(withIdentifier: "cellStatus", for: indexPath as IndexPath)
+        cell.selectionStyle = .none
+        cell.backgroundColor = .clear
+        
+        if indexPath.row != 0 {
+            cell.backgroundColor = .white
+            var content = cell.defaultContentConfiguration()
+            content.textProperties.font = UIFont.systemFont(ofSize: 14)
+            content.imageProperties.maximumSize = CGSize(width: 24, height: 24)
+            
+            let noStatus = UIImageView(frame: CGRect(x: 0, y: cell.frame.height / 2, width: 50, height: 20))
+            noStatus.image = UIImage(systemName: "ellipsis")
+            noStatus.contentMode = .center
+            noStatus.tintColor = .black
+            
+            if indexPath.row == 1 {
+                content.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.systemBlue)
+                content.text = "Read".localized()
+                if dataStatus.count != 0 {
+                    if (dataStatus[0]["time_read"] as! String).isEmpty {
+                        cell.accessoryView = noStatus
+                    } else {
+                        let date = Date(milliseconds: Int64(dataStatus[0]["time_read"] as! String) ?? 100)
+                        let formatter = DateFormatter()
+                        formatter.dateFormat = "HH:mm"
+                        formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
+                        let time = formatter.string(from: date as Date)
+                        
+                        let viewTimeStatus = UIView()
+                        viewTimeStatus.frame = CGRect(x: 0, y: 0, width: 80, height: cell.frame.height)
+                        
+                        let titleTime = UILabel()
+                        viewTimeStatus.addSubview(titleTime)
+                        titleTime.anchor(centerX: viewTimeStatus.centerXAnchor, centerY: viewTimeStatus.centerYAnchor)
+                        titleTime.font = .systemFont(ofSize: 12)
+                        titleTime.text = "\(chatDate(stringDate: dataStatus[0]["time_read"] as! String)) \(time)"
+                        
+                        cell.accessoryView = viewTimeStatus
+                    }
+                }
+            } else {
+                content.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
+                content.text = "Delivered".localized()
+                if (dataStatus[0]["time_delivered"] as! String).isEmpty {
+                    cell.accessoryView = noStatus
+                } else {
+                    let date = Date(milliseconds: Int64(dataStatus[0]["time_delivered"] as! String) ?? 100)
+                    let formatter = DateFormatter()
+                    formatter.dateFormat = "HH:mm"
+                    formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
+                    let time = formatter.string(from: date as Date)
+                    
+                    let viewTimeStatus = UIView()
+                    viewTimeStatus.frame = CGRect(x: 0, y: 0, width: 80, height: cell.frame.height)
+                    
+                    let titleTime = UILabel()
+                    viewTimeStatus.addSubview(titleTime)
+                    titleTime.anchor(centerX: viewTimeStatus.centerXAnchor, centerY: viewTimeStatus.centerYAnchor)
+                    titleTime.font = .systemFont(ofSize: 12)
+                    titleTime.text = "\(chatDate(stringDate: dataStatus[0]["time_delivered"] as! String)) \(time)"
+                    
+                    cell.accessoryView = viewTimeStatus
+                }
+            }
+            cell.contentConfiguration = content
+        } else {
+            cell.textLabel!.text = "TEST"
+        }
+        return cell
+    }
+    
+    private func chatDate(stringDate: String) -> String {
+        let date = Date(milliseconds: Int64(stringDate)!)
+        let calendar = Calendar.current
+        if (calendar.isDateInToday(date)) {
+            return "Today".localized()
+        } else {
+            let startOfNow = calendar.startOfDay(for: Date())
+            let startOfTimeStamp = calendar.startOfDay(for: date)
+            let components = calendar.dateComponents([.day], from: startOfNow, to: startOfTimeStamp)
+            let day = -(components.day!)
+            if day == 1 {
+                return "Yesterday".localized()
+            } else {
+                let formatter = DateFormatter()
+                formatter.dateFormat = "dd/MM/yy"
+                formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
+                let stringFormat = formatter.string(from: date as Date)
+                return stringFormat
+            }
+        }
+    }
+
+}

部分文件因为文件数量过多而无法显示