Przeglądaj źródła

update fix bugs and apn message

alqindiirsyam 4 miesięcy temu
rodzic
commit
669bcdf351

+ 32 - 24
AppBuilder/AppBuilder/SecondTabViewController.swift

@@ -868,36 +868,44 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
             let previousChat = self.chats
             let allChats = Chat.getData()
             var tempChats: [Chat] = []
+
             for singleChat in allChats {
-                if !singleChat.groupId.isEmpty {
-                    let chatParentInPreviousChats = previousChat.first(where: { $0.isParent && $0.groupId == singleChat.groupId })
-                    if self.chatGroupMaps[singleChat.groupId] != nil {
-                        self.chatGroupMaps[singleChat.groupId]!.insert(singleChat, at: 0)
-                        if let parentChat = tempChats.first(where: { $0.groupId == singleChat.groupId && $0.isParent }) {
-                            let counterParent = parentChat.counter
-                            parentChat.counter = "\(Int(counterParent)! + Int(singleChat.counter)!)"
-                        }
-                        if let parentExist = chatParentInPreviousChats, parentExist.isSelected {
-                            if let indexParent = tempChats.firstIndex(where: { $0.isParent && $0.groupId == singleChat.groupId }){
-                                tempChats.insert(singleChat, at: indexParent + self.chatGroupMaps[singleChat.groupId]!.count)
-                            }
-                        }
-                    } else {
-                        self.chatGroupMaps[singleChat.groupId] = [singleChat]
-                        let parentChat = Chat(profile: singleChat.profile, groupName: singleChat.groupName, counter: singleChat.counter, groupId: singleChat.groupId)
-                        parentChat.isParent = true
-                        if let parentExist = chatParentInPreviousChats, parentExist.isSelected {
-                            parentChat.isSelected = true
-                            tempChats.append(parentChat)
-                            tempChats.append(singleChat)
-                        } else {
-                            tempChats.append(parentChat)
+                guard !singleChat.groupId.isEmpty else {
+                    tempChats.append(singleChat)
+                    continue
+                }
+                
+                let chatParentInPreviousChats = previousChat.first { $0.isParent && $0.groupId == singleChat.groupId }
+                
+                if var existingGroup = self.chatGroupMaps[singleChat.groupId] {
+                    existingGroup.insert(singleChat, at: 0)
+                    self.chatGroupMaps[singleChat.groupId] = existingGroup
+                    
+                    if let parentChatIndex = tempChats.firstIndex(where: { $0.groupId == singleChat.groupId && $0.isParent }) {
+                        if let counterParent = Int(tempChats[parentChatIndex].counter), let counterSingle = Int(singleChat.counter) {
+                            tempChats[parentChatIndex].counter = "\(counterParent + counterSingle)"
                         }
                     }
+                    
+                    if let parentExist = chatParentInPreviousChats, parentExist.isSelected,
+                       let indexParent = tempChats.firstIndex(where: { $0.isParent && $0.groupId == singleChat.groupId }) {
+                        tempChats.insert(singleChat, at: min(indexParent + existingGroup.count, tempChats.count))
+                    }
                 } else {
-                    tempChats.append(singleChat)
+                    self.chatGroupMaps[singleChat.groupId] = [singleChat]
+                    let parentChat = Chat(profile: singleChat.profile, groupName: singleChat.groupName, counter: singleChat.counter, groupId: singleChat.groupId)
+                    parentChat.isParent = true
+                    
+                    if let parentExist = chatParentInPreviousChats, parentExist.isSelected {
+                        parentChat.isSelected = true
+                        tempChats.append(parentChat)
+                        tempChats.append(singleChat)
+                    } else {
+                        tempChats.append(parentChat)
+                    }
                 }
             }
+
             self.chats = tempChats
             completion()
         }

+ 169 - 122
NexilisLite/NexilisLite/Source/APIS.swift

@@ -882,23 +882,27 @@ public class APIS: NSObject {
     
     public static func sendPushToken(_ token: String, isResend: Bool = false, isCall: Bool = false) {
         DispatchQueue.global().async{
-            var isResend = isResend
             if !isCall {
-                if Utils.getTokenAPN().isEmpty || token != Utils.getTokenAPN() || isResend {
+                if Utils.getTokenAPN().isEmpty || token != Utils.getTokenAPN() {
                     Utils.setTokenAPN(value: token)
-                    isResend = true
                 }
-//                if isResend {
-                    _ = Nexilis.write(message: CoreMessage_TMessageBank.getToken(token: token))
-//                }
+                DispatchQueue.global().async {
+                    while API.nGetCLXConnState() == 0 {
+                        Thread.sleep(forTimeInterval: 1)
+                    }
+                    _ = Nexilis.write(message: CoreMessage_TMessageBank.getToken(token: token, tokenCall: Utils.getTokenCall()))
+                }
             }
             else {
-                if Utils.getTokenCall().isEmpty || token != Utils.getTokenCall() || isResend {
+                if Utils.getTokenCall().isEmpty || token != Utils.getTokenCall() {
                     Utils.setTokenCall(value: token)
-                    isResend = true
                 }
-//                if isResend {
-                    _ = Nexilis.write(message: CoreMessage_TMessageBank.getToken(token: token, isCall: true))
+//                DispatchQueue.global().async {
+//                    while API.nGetCLXConnState() == 0 {
+//                        Thread.sleep(forTimeInterval: 1)
+//                    }
+//                    print("SEND TOKEN CALL")
+//                    _ = Nexilis.write(message: CoreMessage_TMessageBank.getToken(token: token, isCall: true))
 //                }
             }
         }
@@ -908,7 +912,7 @@ public class APIS: NSObject {
     public static var fpinCall: String?
     public static func showNotificationNexilis(_ userInfo: [AnyHashable : Any]) {
         if checkAppStateisBackground() {
-            Nexilis.sendStateToServer(s: "MASUK SHOW NOTIFICATION NEXILIS")
+//            Nexilis.sendStateToServer(s: "MASUK SHOW NOTIFICATION NEXILIS")
             DispatchQueue.main.async {
                 if let payload = userInfo["payload"] as? [String: Any] {
                     if let messagePayload = payload["message"] as? [String: Any] {
@@ -999,7 +1003,7 @@ public class APIS: NSObject {
     
     private static func ackAPN(id: String) {
         DispatchQueue.global().async {
-            Nexilis.sendStateToServer(s: "send ack from apn")
+//            Nexilis.sendStateToServer(s: "send ack from apn")
             DispatchQueue.global().async {
                 let parameter: [String : Any] = [
                     "pin": User.getMyPin() ?? "",
@@ -1167,6 +1171,9 @@ public class APIS: NSObject {
                 sourceURL = Bundle.resourcesMediaBundle(for: Nexilis.self).url(forResource: nameSound, withExtension: "mp3")
             }
         }
+        if sourceURL == nil {
+            return
+        }
         let fileManager = FileManager.default
         let soundDirectory = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first!.appendingPathComponent("Sounds", isDirectory: true)
         if !fileManager.fileExists(atPath: soundDirectory.path) {
@@ -1192,139 +1199,179 @@ public class APIS: NSObject {
             if let userInfo = response.notification.request.content.userInfo as? [String: String] {
                 let id = userInfo["id"] ?? ""
                 let type = userInfo["type"] ?? ""
+                let callType = userInfo["callType"] ?? ""
                 if let navigationC = UIApplication.shared.visibleViewController as? UINavigationController {
                     if navigationC.viewControllers[navigationC.viewControllers.count - 1] is EditorPersonal || navigationC.viewControllers[navigationC.viewControllers.count - 1] is EditorGroup {
                         navigationC.popViewController(animated: true)
                     }
                 }
-                if type == "0" {
-                    if User.getDataCanNil(pin: id) == nil {
-                        return
-                    }
-                    let editorPersonalVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorPersonalVC") as! EditorPersonal
-                    editorPersonalVC.hidesBottomBarWhenPushed = true
-                    editorPersonalVC.unique_l_pin = id
-                    editorPersonalVC.fromNotification = true
-                    let navigationController = CustomNavigationController(rootViewController: editorPersonalVC)
-                    navigationController.modalPresentationStyle = .fullScreen
-                    navigationController.navigationBar.tintColor = .white
-                    navigationController.navigationBar.barTintColor = UIApplication.shared.visibleViewController?.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
-                    navigationController.navigationBar.isTranslucent = false
-                    navigationController.navigationBar.overrideUserInterfaceStyle = .dark
-                    navigationController.navigationBar.barStyle = .black
-                    let cancelButtonAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
-                    UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes, for: .normal)
-                    let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
-                    navigationController.navigationBar.titleTextAttributes = textAttributes
-                    if UIApplication.shared.visibleViewController is UINavigationController && Nexilis.fromMAB {
-                        editorPersonalVC.fromNotification = false
-                        UIApplication.shared.visibleViewController?.show(editorPersonalVC, sender: nil)
-                    } else {
-                        UIApplication.shared.visibleViewController?.present(navigationController, animated: true, completion: nil)
-                    }
-                } else if type == "1" {
-                    var groupExist = false
-                    Database.shared.database?.inTransaction({ (fmdb, rollback) in
-                        var idGroup = ""
-                        if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "SELECT title, group_id FROM DISCUSSION_FORUM WHERE chat_id='\(id)'"), cursor.next() {
-                            groupExist = true
-                            cursor.close()
-                        } else {
-                            if idGroup.isEmpty {
-                                idGroup = id
-                            }
-                            if let cursorGroup = Database.shared.getRecords(fmdb: fmdb, query: "SELECT f_name, image_id FROM GROUPZ WHERE group_id='\(idGroup)'"), cursorGroup.next() {
-                                groupExist = true
-                                cursorGroup.close()
+                showEditorOrCallFromAPN(id, type, callType)
+            } else {
+                let userInfo = response.notification.request.content.userInfo
+                DispatchQueue.main.async {
+                    if let payload = userInfo["payload"] as? [String: Any] {
+                        if let messagePayload = payload["message"] as? [String: Any] {
+                            if let data = messagePayload["data"] as? [String: Any] {
+                                let code = data["nx_code"] as? String ?? ""
+                                if code == "CL01" {
+                                    if let message = data["bodies"] as? [String: String] {
+                                        let idAck = data["message_id"] as? String ?? ""
+                                        let messageToSave = TMessage()
+                                        messageToSave.mBodies = message
+                                        do {
+                                            var messageExist = false
+                                            Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                                                if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select message_id from MESSAGE where message_id = '\(message[CoreMessage_TMessageKey.MESSAGE_ID] ?? "")'"), cursor.next() {
+                                                    messageExist = true
+                                                    cursor.close()
+                                                }
+                                            })
+                                            if messageExist {
+                                                ackAPN(id: idAck)
+                                                return
+                                            }
+                                        } catch {
+                                            print("error saving message: \(error)")
+                                        }
+                                        ackAPN(id: idAck)
+                                        Nexilis.saveMessage(message: messageToSave, withStatus: false, fromAPNS: true)
+                                        NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
+                                    }
+                                }
                             }
                         }
-                    })
-                    if !groupExist {
-                        return
                     }
-                    let editorGroupVC = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "editorGroupVC") as! EditorGroup
-                    editorGroupVC.hidesBottomBarWhenPushed = true
-                    editorGroupVC.unique_l_pin = id
-                    editorGroupVC.fromNotification = true
-                    let navigationController = CustomNavigationController(rootViewController: editorGroupVC)
-                    navigationController.modalPresentationStyle = .fullScreen
-                    navigationController.navigationBar.tintColor = .white
-                    navigationController.navigationBar.barTintColor = UIApplication.shared.visibleViewController?.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
-                    navigationController.navigationBar.isTranslucent = false
-                    navigationController.navigationBar.overrideUserInterfaceStyle = .dark
-                    navigationController.navigationBar.barStyle = .black
-                    let cancelButtonAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
-                    UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes, for: .normal)
-                    let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
-                    navigationController.navigationBar.titleTextAttributes = textAttributes
-                    if UIApplication.shared.visibleViewController is UINavigationController && Nexilis.fromMAB {
-                        editorGroupVC.fromNotification = false
-                        UIApplication.shared.visibleViewController?.show(editorGroupVC, sender: nil)
-                    } else {
-                        UIApplication.shared.visibleViewController?.present(navigationController, animated: true, completion: nil)
+                }
+            }
+        }
+//        UNUserNotificationCenter.current().removeAllDeliveredNotifications()
+    }
+    
+    private static func showEditorOrCallFromAPN(_ id: String, _ type: String, _ callType: String) {
+        if type == "0" {
+            if User.getDataCanNil(pin: id) == nil {
+                return
+            }
+            let editorPersonalVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorPersonalVC") as! EditorPersonal
+            editorPersonalVC.hidesBottomBarWhenPushed = true
+            editorPersonalVC.unique_l_pin = id
+            editorPersonalVC.fromNotification = true
+            let navigationController = CustomNavigationController(rootViewController: editorPersonalVC)
+            navigationController.modalPresentationStyle = .fullScreen
+            navigationController.navigationBar.tintColor = .white
+            navigationController.navigationBar.barTintColor = UIApplication.shared.visibleViewController?.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
+            navigationController.navigationBar.isTranslucent = false
+            navigationController.navigationBar.overrideUserInterfaceStyle = .dark
+            navigationController.navigationBar.barStyle = .black
+            let cancelButtonAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
+            UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes, for: .normal)
+            let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
+            navigationController.navigationBar.titleTextAttributes = textAttributes
+            if UIApplication.shared.visibleViewController is UINavigationController && Nexilis.fromMAB {
+                editorPersonalVC.fromNotification = false
+                UIApplication.shared.visibleViewController?.show(editorPersonalVC, sender: nil)
+            } else {
+                UIApplication.shared.visibleViewController?.present(navigationController, animated: true, completion: nil)
+            }
+        } else if type == "1" {
+            var groupExist = false
+            Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                var idGroup = ""
+                if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "SELECT title, group_id FROM DISCUSSION_FORUM WHERE chat_id='\(id)'"), cursor.next() {
+                    groupExist = true
+                    cursor.close()
+                } else {
+                    if idGroup.isEmpty {
+                        idGroup = id
                     }
-                } else if type == "CL03" {
-                    Nexilis.stopRingtoneCall()
-                    if !Nexilis.callAPNActivated {
-                        return
+                    if let cursorGroup = Database.shared.getRecords(fmdb: fmdb, query: "SELECT f_name, image_id FROM GROUPZ WHERE group_id='\(idGroup)'"), cursorGroup.next() {
+                        groupExist = true
+                        cursorGroup.close()
                     }
-                    let callType = userInfo["callType"] ?? ""
-                    if callType == "1" {
-                        if let user = User.getData(pin: id), user.firstName == "User".localized() {
-                            return
-                        }
-                        let controller = QmeraAudioViewController()
-                        controller.isOutgoing = false
-                        controller.user = User.getData(pin: id)
-                        controller.autoAcceptAPN = true
-                        controller.modalPresentationStyle = .overCurrentContext
-                        if UIApplication.shared.visibleViewController is UIAlertController {
-                            let vc = UIApplication.shared.visibleViewController as! UIAlertController
-                            vc.dismiss(animated: true, completion: {
-                                if UIApplication.shared.visibleViewController?.navigationController != nil {
-                                    UIApplication.shared.visibleViewController?.navigationController?.present(controller, animated: true, completion: nil)
-                                } else {
-                                    UIApplication.shared.visibleViewController?.present(controller, animated: true, completion: nil)
-                                }
-                            })
-                            return
-                        }
+                }
+            })
+            if !groupExist {
+                return
+            }
+            let editorGroupVC = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "editorGroupVC") as! EditorGroup
+            editorGroupVC.hidesBottomBarWhenPushed = true
+            editorGroupVC.unique_l_pin = id
+            editorGroupVC.fromNotification = true
+            let navigationController = CustomNavigationController(rootViewController: editorGroupVC)
+            navigationController.modalPresentationStyle = .fullScreen
+            navigationController.navigationBar.tintColor = .white
+            navigationController.navigationBar.barTintColor = UIApplication.shared.visibleViewController?.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
+            navigationController.navigationBar.isTranslucent = false
+            navigationController.navigationBar.overrideUserInterfaceStyle = .dark
+            navigationController.navigationBar.barStyle = .black
+            let cancelButtonAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
+            UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes, for: .normal)
+            let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
+            navigationController.navigationBar.titleTextAttributes = textAttributes
+            if UIApplication.shared.visibleViewController is UINavigationController && Nexilis.fromMAB {
+                editorGroupVC.fromNotification = false
+                UIApplication.shared.visibleViewController?.show(editorGroupVC, sender: nil)
+            } else {
+                UIApplication.shared.visibleViewController?.present(navigationController, animated: true, completion: nil)
+            }
+        } else if type == "CL03" {
+            Nexilis.stopRingtoneCall()
+            if !Nexilis.callAPNActivated {
+                return
+            }
+            if callType == "1" {
+                if let user = User.getData(pin: id), user.firstName == "User".localized() {
+                    return
+                }
+                let controller = QmeraAudioViewController()
+                controller.isOutgoing = false
+                controller.user = User.getData(pin: id)
+                controller.autoAcceptAPN = true
+                controller.modalPresentationStyle = .overCurrentContext
+                if UIApplication.shared.visibleViewController is UIAlertController {
+                    let vc = UIApplication.shared.visibleViewController as! UIAlertController
+                    vc.dismiss(animated: true, completion: {
                         if UIApplication.shared.visibleViewController?.navigationController != nil {
                             UIApplication.shared.visibleViewController?.navigationController?.present(controller, animated: true, completion: nil)
                         } else {
                             UIApplication.shared.visibleViewController?.present(controller, animated: true, completion: nil)
                         }
-                    } else {
-                        if let user = User.getData(pin: id), user.firstName == "User".localized() {
-                            return
-                        }
-                        let videoController = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "videoVCQmera") as! QmeraVideoViewController
-                        videoController.fPin = id
-                        videoController.isInisiator = false
-                        videoController.autoAcceptAPN = true
-                        let navigationController = CustomNavigationController(rootViewController: videoController)
-                        navigationController.modalPresentationStyle = .fullScreen
-                        if UIApplication.shared.visibleViewController is UIAlertController {
-                            let vc = UIApplication.shared.visibleViewController as! UIAlertController
-                            vc.dismiss(animated: true, completion: {
-                                if UIApplication.shared.visibleViewController?.navigationController != nil {
-                                    UIApplication.shared.visibleViewController?.navigationController?.present(navigationController, animated: true, completion: nil)
-                                } else {
-                                    UIApplication.shared.visibleViewController?.present(navigationController, animated: true, completion: nil)
-                                }
-                            })
-                            return
-                        }
+                    })
+                    return
+                }
+                if UIApplication.shared.visibleViewController?.navigationController != nil {
+                    UIApplication.shared.visibleViewController?.navigationController?.present(controller, animated: true, completion: nil)
+                } else {
+                    UIApplication.shared.visibleViewController?.present(controller, animated: true, completion: nil)
+                }
+            } else {
+                if let user = User.getData(pin: id), user.firstName == "User".localized() {
+                    return
+                }
+                let videoController = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "videoVCQmera") as! QmeraVideoViewController
+                videoController.fPin = id
+                videoController.isInisiator = false
+                videoController.autoAcceptAPN = true
+                let navigationController = CustomNavigationController(rootViewController: videoController)
+                navigationController.modalPresentationStyle = .fullScreen
+                if UIApplication.shared.visibleViewController is UIAlertController {
+                    let vc = UIApplication.shared.visibleViewController as! UIAlertController
+                    vc.dismiss(animated: true, completion: {
                         if UIApplication.shared.visibleViewController?.navigationController != nil {
                             UIApplication.shared.visibleViewController?.navigationController?.present(navigationController, animated: true, completion: nil)
                         } else {
                             UIApplication.shared.visibleViewController?.present(navigationController, animated: true, completion: nil)
                         }
-                    }
+                    })
+                    return
+                }
+                if UIApplication.shared.visibleViewController?.navigationController != nil {
+                    UIApplication.shared.visibleViewController?.navigationController?.present(navigationController, animated: true, completion: nil)
+                } else {
+                    UIApplication.shared.visibleViewController?.present(navigationController, animated: true, completion: nil)
                 }
             }
         }
-//        UNUserNotificationCenter.current().removeAllDeliveredNotifications()
     }
     
     public static func checkAppStateisBackground() -> Bool {

+ 3 - 7
NexilisLite/NexilisLite/Source/CoreMessage_TMessageBank.swift

@@ -2613,18 +2613,14 @@ public class CoreMessage_TMessageBank {
         return tMessage
     }
     
-    public static func getToken(token: String, isCall: Bool = false) -> TMessage {
+    public static func getToken(token: String, tokenCall: String) -> TMessage {
         let tMessage = NexilisLite.TMessage()
         let me = User.getMyPin() ?? ""
         tMessage.mPIN = me
         tMessage.mCode = CoreMessage_TMessageCode.APN_TOKEN
         tMessage.mStatus = CoreMessage_TMessageUtil.getTID()
-        if !isCall {
-            tMessage.mBodies[CoreMessage_TMessageKey.TOKEN] = token
-        }
-        else {
-            tMessage.mBodies[CoreMessage_TMessageKey.CALL_TOKEN] = token
-        }
+        tMessage.mBodies[CoreMessage_TMessageKey.TOKEN] = token
+        tMessage.mBodies[CoreMessage_TMessageKey.CALL_TOKEN] = tokenCall
         tMessage.mBodies[CoreMessage_TMessageKey.DEVICE_BRAND] = "iOS"
         tMessage.mBodies[CoreMessage_TMessageKey.ANDROID_ID] = Utils.M_USER_ANDROID_ID
         return tMessage

+ 4 - 0
NexilisLite/NexilisLite/Source/Extension.swift

@@ -829,6 +829,10 @@ extension String {
         }
     }
     
+    func distance(of index: String.Index) -> Int? {
+        return utf16.distance(from: startIndex, to: index)
+    }
+    
 }
 
 extension UIFont {

+ 5 - 3
NexilisLite/NexilisLite/Source/IncomingThread.swift

@@ -1317,9 +1317,11 @@ class IncomingThread {
         } else {
             Nexilis.saveMessage(message: message, withStatus: false)
         }
-        if APIS.checkAppStateisBackground() {
-            APIS.addNotificationNexilis(message)
-            ackAPN(id: message.mStatus)
+        DispatchQueue.main.async { [self] in
+            if APIS.checkAppStateisBackground() {
+                APIS.addNotificationNexilis(message)
+                ackAPN(id: message.mStatus)
+            }
         }
         //print("save message incoming")
         ack(message: message)

+ 8 - 8
NexilisLite/NexilisLite/Source/View/Call/QmeraVideoViewController.swift

@@ -638,9 +638,9 @@ class QmeraVideoViewController: UIViewController {
             alert.addAction(UIAlertAction(title: "Yes".localized(), style: UIAlertAction.Style.default, handler: {(_) in
                 Nexilis.stopRingtoneCall()
                 Nexilis.stopRingbacktoneCall()
-                if self.labelIncomingOutgoing.isDescendant(of: self.view) {
-                    self.labelIncomingOutgoing.text = "Video call is over".localized()
-                }
+//                if self.labelIncomingOutgoing.isDescendant(of: self.view) {
+//                    self.labelIncomingOutgoing.text = "Video call is over".localized()
+//                }
                 if self.stackViewToolbar.isDescendant(of: self.view){
                     self.stackViewToolbar.removeFromSuperview()
                 }
@@ -670,7 +670,7 @@ class QmeraVideoViewController: UIViewController {
                 }
                 self.wbTimer.invalidate()
                 self.vcTimer.invalidate()
-                self.labelTimerVC.text = "Video call is over".localized()
+//                self.labelTimerVC.text = "Video call is over".localized()
                 QmeraVideoViewController.isLoop = false
                 self.endAllCall()
                 DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
@@ -1392,9 +1392,9 @@ class QmeraVideoViewController: UIViewController {
             }
             DispatchQueue.main.async {
                 if (self.dataPerson.count == 1) {
-                    if self.labelIncomingOutgoing.isDescendant(of: self.view) {
-                        self.labelIncomingOutgoing.text = "Video call is over".localized()
-                    }
+//                    if self.labelIncomingOutgoing.isDescendant(of: self.view) {
+//                        self.labelIncomingOutgoing.text = "Video call is over".localized()
+//                    }
                     if self.stackViewToolbar.isDescendant(of: self.view){
                         self.stackViewToolbar.removeFromSuperview()
                     }
@@ -1424,7 +1424,7 @@ class QmeraVideoViewController: UIViewController {
                     }
                     self.wbTimer.invalidate()
                     self.vcTimer.invalidate()
-                    self.labelTimerVC.text = "Video call is over".localized()
+//                    self.labelTimerVC.text = "Video call is over".localized()
                     _ = Nexilis.getWhiteboardDelegate()?.terminate()
                     let controller = self.presentedViewController
                     if controller != nil {

+ 31 - 19
NexilisLite/NexilisLite/Source/View/Chat/EditorGroup.swift

@@ -1720,9 +1720,11 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
     }
     
     @objc func showChooserACKConfidential() {
+        dismissKeyboard()
         let alertController = LibAlertController(title: "Message Mode".localized(), message: "Select".localized() + " " + "Message Mode".localized(), preferredStyle: .actionSheet)
         let imageConfidential = resizeImage(image: UIImage(named: "pb_icon_conf_msg_on", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
         let imageAck = resizeImage(image: UIImage(named: "pb_icon_ack_msg_on", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
+        let imageSticker = resizeImage(image: UIImage(named: "Sticker---Emoji", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
         let confidentialAction = UIAlertAction(title: "Confidential Message".localized(), style: .default, handler: { (UIAlertAction) in
             self.isConfidential = !self.isConfidential
             if self.isConfidential {
@@ -1745,10 +1747,15 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
                 self.isConfidential = false
             }
         })
+        let stickerAction = UIAlertAction(title: "Open Sticker".localized(), style: .default, handler: { (UIAlertAction) in
+            self.stickerTapped(UIButton())
+        })
         confidentialAction.setValue(imageConfidential, forKey: "image")
         ackAction.setValue(imageAck, forKey: "image")
+        stickerAction.setValue(imageSticker, forKey: "image")
         alertController.addAction(confidentialAction)
         alertController.addAction(ackAction)
+        alertController.addAction(stickerAction)
         alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: { (UIAlertAction) in
             self.isConfidential = false
             self.isAck = false
@@ -3021,47 +3028,51 @@ extension EditorGroup: UITextViewDelegate, CustomTextViewPasteDelegate {
             }
         }
         
-        let nsText = textView.text as NSString? ?? ""
+        guard let nsText = textView.text as NSString? else { return true }
         let newText = nsText.replacingCharacters(in: range, with: text)
-        var lines = textView.text.components(separatedBy: "\n")
+        var lines = newText.components(separatedBy: "\n")
         
-        let affectedLineIndex = textView.text[..<textView.text.index(textView.text.startIndex, offsetBy: range.location)].components(separatedBy: "\n").count - 1
+        // Ensure range location is valid, considering Unicode scalars
+        guard let textRange = Range(range, in: textView.text) else { return true }
+        let prefixText = textView.text[..<textRange.lowerBound]
+        let affectedLineIndex = prefixText.components(separatedBy: "\n").count - 1
         guard affectedLineIndex >= 0, affectedLineIndex < lines.count else { return true }
         
         let affectedLine = lines[affectedLineIndex]
-
+        
         // Prevent deleting two-space indentation before bullet/number
         if affectedLine.hasPrefix("  •") || affectedLine.range(of: #"^\s{2}\d+\."#, options: .regularExpression) != nil {
-            let startIndex = textView.text.distance(from: textView.text.startIndex, to: textView.text.range(of: affectedLine)?.lowerBound ?? textView.text.startIndex)
-            
-            if range.location == startIndex || range.location == startIndex + 1 {
-                return false
+            if let lineStart = textView.text.range(of: affectedLine)?.lowerBound,
+               let startIndex = textView.text.distance(of: lineStart) {
+                if range.location == startIndex || range.location == startIndex + 1 {
+                    return false
+                }
             }
         }
-
+        
         // Auto-indent new lines based on previous line
         if text == "\n" {
             let previousLine = lines[affectedLineIndex]
-
+            
             if previousLine.hasPrefix("  •") {
                 let newBullet = "\n  • "
                 textView.text = nsText.replacingCharacters(in: range, with: newBullet)
-                textView.selectedRange = NSRange(location: range.location + newBullet.count, length: 0)
+                textView.selectedRange = NSRange(location: range.location + newBullet.utf16.count, length: 0)
                 return false
             }
-
+            
             if let match = previousLine.range(of: #"^\s{2}(\d+)\."#, options: .regularExpression),
                let numberMatch = previousLine[match].components(separatedBy: ".").first,
                let number = Int(numberMatch.trimmingCharacters(in: .whitespaces)) {
-
+                
                 let newNumber = "\n  \(number + 1). "
                 textView.text = nsText.replacingCharacters(in: range, with: newNumber)
-                textView.selectedRange = NSRange(location: range.location + newNumber.count, length: 0)
+                textView.selectedRange = NSRange(location: range.location + newNumber.utf16.count, length: 0)
                 return false
             }
         }
-
-        // **Handle Backspace on Empty Bullet (Convert "  • " → "- ")**
+        
+        // Handle Backspace on Empty Bullet (Convert "  • " → "- ")
         if text.isEmpty && affectedLine.trimmingCharacters(in: .whitespaces) == "•" {
             lines[affectedLineIndex] = "- "  // Replace "  • " with "- "
             textView.text = lines.joined(separator: "\n")
@@ -3069,8 +3080,9 @@ extension EditorGroup: UITextViewDelegate, CustomTextViewPasteDelegate {
             return false
         }
         
-        if text.isEmpty, let numberMatch = affectedLine.range(of: #"^\s{2}(\d+)\.$"#, options: .regularExpression) {
-            lines[affectedLineIndex] = "\(affectedLine.trimmingCharacters(in: .whitespaces))" // Remove indent
+        // Handle Backspace on Numbered List
+        if text.isEmpty, affectedLine.range(of: #"^\s{2}(\d+)\.$"#, options: .regularExpression) != nil {
+            lines[affectedLineIndex] = affectedLine.trimmingCharacters(in: .whitespaces)
             textView.text = lines.joined(separator: "\n")
             textView.selectedRange = NSRange(location: range.location - 1, length: 0)
             return false
@@ -4867,7 +4879,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                     textChat = textChat.components(separatedBy: "■")[0]
                     textChat = textChat.trimmingCharacters(in: .whitespacesAndNewlines)
                 }
-                if !fileChat.isEmpty && dataMessages[indexPath.row]["lock"] as? String != "2" {
+                if !fileChat.isEmpty && dataMessages[indexPath.row]["lock"] as? String != "1" && dataMessages[indexPath.row]["lock"] as? String != "2" {
                     textChat = textChat.components(separatedBy: "|")[1]
                 }
                 let finalAtribute = textChat.richText(group_id: self.dataGroup["group_id"]  as? String ?? "")

+ 32 - 19
NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift

@@ -2258,10 +2258,12 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
     }
     
     @objc func showChooserACKConfidential() {
+        dismissKeyboard()
         let alertController = LibAlertController(title: "Message Mode".localized(), message: "Select".localized() + " " + "Message Mode".localized(), preferredStyle: .actionSheet)
         let imageConfidential = resizeImage(image: UIImage(named: "pb_icon_conf_msg_on", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
         let imageAck = resizeImage(image: UIImage(named: "pb_icon_ack_msg_on", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
         let imageSecret = resizeImage(image: UIImage(named: "pb_icon_secret_msg_on", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
+        let imageSticker = resizeImage(image: UIImage(named: "Sticker---Emoji", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
         let confidentialAction = UIAlertAction(title: "Confidential Message".localized(), style: .default, handler: { (UIAlertAction) in
             self.isConfidential = !self.isConfidential
             if self.isConfidential {
@@ -2304,12 +2306,18 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                 self.isAck = false
             }
         })
+        let stickerAction = UIAlertAction(title: "Open Sticker".localized(), style: .default, handler: { (UIAlertAction) in
+            self.stickerTapped(UIButton())
+        })
         confidentialAction.setValue(imageConfidential, forKey: "image")
         ackAction.setValue(imageAck, forKey: "image")
         secretAction.setValue(imageSecret, forKey: "image")
+        secretAction.setValue(imageSecret, forKey: "image")
+        stickerAction.setValue(imageSticker, forKey: "image")
         alertController.addAction(confidentialAction)
         alertController.addAction(ackAction)
         alertController.addAction(secretAction)
+        alertController.addAction(stickerAction)
         alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: { (UIAlertAction) in
             self.isConfidential = false
             self.isAck = false
@@ -4042,47 +4050,51 @@ extension EditorPersonal: UITextViewDelegate, CustomTextViewPasteDelegate {
     }
     
     public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
-        let nsText = textView.text as NSString? ?? ""
+        guard let nsText = textView.text as NSString? else { return true }
         let newText = nsText.replacingCharacters(in: range, with: text)
-        var lines = textView.text.components(separatedBy: "\n")
+        var lines = newText.components(separatedBy: "\n")
         
-        let affectedLineIndex = textView.text[..<textView.text.index(textView.text.startIndex, offsetBy: range.location)].components(separatedBy: "\n").count - 1
+        // Ensure range location is valid, considering Unicode scalars
+        guard let textRange = Range(range, in: textView.text) else { return true }
+        let prefixText = textView.text[..<textRange.lowerBound]
+        let affectedLineIndex = prefixText.components(separatedBy: "\n").count - 1
         guard affectedLineIndex >= 0, affectedLineIndex < lines.count else { return true }
         
         let affectedLine = lines[affectedLineIndex]
-
+        
         // Prevent deleting two-space indentation before bullet/number
         if affectedLine.hasPrefix("  •") || affectedLine.range(of: #"^\s{2}\d+\."#, options: .regularExpression) != nil {
-            let startIndex = textView.text.distance(from: textView.text.startIndex, to: textView.text.range(of: affectedLine)?.lowerBound ?? textView.text.startIndex)
-            
-            if range.location == startIndex || range.location == startIndex + 1 {
-                return false
+            if let lineStart = textView.text.range(of: affectedLine)?.lowerBound,
+               let startIndex = textView.text.distance(of: lineStart) {
+                if range.location == startIndex || range.location == startIndex + 1 {
+                    return false
+                }
             }
         }
-
+        
         // Auto-indent new lines based on previous line
         if text == "\n" {
             let previousLine = lines[affectedLineIndex]
-
+            
             if previousLine.hasPrefix("  •") {
                 let newBullet = "\n  • "
                 textView.text = nsText.replacingCharacters(in: range, with: newBullet)
-                textView.selectedRange = NSRange(location: range.location + newBullet.count, length: 0)
+                textView.selectedRange = NSRange(location: range.location + newBullet.utf16.count, length: 0)
                 return false
             }
-
+            
             if let match = previousLine.range(of: #"^\s{2}(\d+)\."#, options: .regularExpression),
                let numberMatch = previousLine[match].components(separatedBy: ".").first,
                let number = Int(numberMatch.trimmingCharacters(in: .whitespaces)) {
-
+                
                 let newNumber = "\n  \(number + 1). "
                 textView.text = nsText.replacingCharacters(in: range, with: newNumber)
-                textView.selectedRange = NSRange(location: range.location + newNumber.count, length: 0)
+                textView.selectedRange = NSRange(location: range.location + newNumber.utf16.count, length: 0)
                 return false
             }
         }
-
-        // **Handle Backspace on Empty Bullet (Convert "  • " → "- ")**
+        
+        // Handle Backspace on Empty Bullet (Convert "  • " → "- ")
         if text.isEmpty && affectedLine.trimmingCharacters(in: .whitespaces) == "•" {
             lines[affectedLineIndex] = "- "  // Replace "  • " with "- "
             textView.text = lines.joined(separator: "\n")
@@ -4090,8 +4102,9 @@ extension EditorPersonal: UITextViewDelegate, CustomTextViewPasteDelegate {
             return false
         }
         
-        if text.isEmpty, let numberMatch = affectedLine.range(of: #"^\s{2}(\d+)\.$"#, options: .regularExpression) {
-            lines[affectedLineIndex] = "\(affectedLine.trimmingCharacters(in: .whitespaces))" // Remove indent
+        // Handle Backspace on Numbered List
+        if text.isEmpty, affectedLine.range(of: #"^\s{2}(\d+)\.$"#, options: .regularExpression) != nil {
+            lines[affectedLineIndex] = affectedLine.trimmingCharacters(in: .whitespaces)
             textView.text = lines.joined(separator: "\n")
             textView.selectedRange = NSRange(location: range.location - 1, length: 0)
             return false
@@ -6105,7 +6118,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                     textChat = textChat.components(separatedBy: "■")[0]
                     textChat = textChat.trimmingCharacters(in: .whitespacesAndNewlines)
                 }
-                if !fileChat.isEmpty && dataMessages[indexPath.row]["lock"] as? String != "2" {
+                if !fileChat.isEmpty && dataMessages[indexPath.row]["lock"] as? String != "1" && dataMessages[indexPath.row]["lock"] as? String != "2" {
                     textChat = textChat.components(separatedBy: "|")[1]
                 }
                 let finalAtribute = textChat.richText()