Browse Source

update fix bugs

alqindiirsyam 4 months ago
parent
commit
3cb52fff14

+ 10 - 2
AppBuilder/AppBuilder/SecondTabViewController.swift

@@ -1085,11 +1085,17 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
                 smartChatVC.fromNotification = false
                 smartChatVC.fromNotification = false
                 navigationController?.show(smartChatVC, sender: nil)
                 navigationController?.show(smartChatVC, sender: nil)
             } else if data.messageScope == "3" {
             } else if data.messageScope == "3" {
+                if data.pin.isEmpty {
+                    return
+                }
                 let editorPersonalVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorPersonalVC") as! EditorPersonal
                 let editorPersonalVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorPersonalVC") as! EditorPersonal
                 editorPersonalVC.hidesBottomBarWhenPushed = true
                 editorPersonalVC.hidesBottomBarWhenPushed = true
                 editorPersonalVC.unique_l_pin = data.pin
                 editorPersonalVC.unique_l_pin = data.pin
                 navigationController?.show(editorPersonalVC, sender: nil)
                 navigationController?.show(editorPersonalVC, sender: nil)
             } else {
             } else {
+                if data.pin.isEmpty {
+                    return
+                }
                 let editorGroupVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorGroupVC") as! EditorGroup
                 let editorGroupVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorGroupVC") as! EditorGroup
                 editorGroupVC.hidesBottomBarWhenPushed = true
                 editorGroupVC.hidesBottomBarWhenPushed = true
                 editorGroupVC.unique_l_pin = data.pin
                 editorGroupVC.unique_l_pin = data.pin
@@ -1114,8 +1120,10 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
             for dataSubChat in self.chatGroupMaps[data.groupId]! {
             for dataSubChat in self.chatGroupMaps[data.groupId]! {
                 if var indexParent = chats.firstIndex(where: { $0.isParent && $0.groupId == data.groupId }) {
                 if var indexParent = chats.firstIndex(where: { $0.isParent && $0.groupId == data.groupId }) {
                     if isFiltering || selectedTag == UNREAD_TAG {
                     if isFiltering || selectedTag == UNREAD_TAG {
-                        fillteredData.insert(dataSubChat, at: indexParent + 1)
-                        indexParent+=1
+                        if var indexParentFilter = fillteredData.firstIndex(where: { ($0 as! Chat).isParent && ($0 as! Chat).groupId == data.groupId }) {
+                            fillteredData.insert(dataSubChat, at: indexParentFilter + 1)
+                            indexParentFilter+=1
+                        }
                     } else {
                     } else {
                         chats.insert(dataSubChat, at: indexParent + 1)
                         chats.insert(dataSubChat, at: indexParent + 1)
                         indexParent+=1
                         indexParent+=1

+ 23 - 19
NexilisLite/NexilisLite/Source/APIS.swift

@@ -888,18 +888,18 @@ public class APIS: NSObject {
                     Utils.setTokenAPN(value: token)
                     Utils.setTokenAPN(value: token)
                     isResend = true
                     isResend = true
                 }
                 }
-                if isResend {
+//                if isResend {
                     _ = Nexilis.write(message: CoreMessage_TMessageBank.getToken(token: token))
                     _ = Nexilis.write(message: CoreMessage_TMessageBank.getToken(token: token))
-                }
+//                }
             }
             }
             else {
             else {
                 if Utils.getTokenCall().isEmpty || token != Utils.getTokenCall() || isResend {
                 if Utils.getTokenCall().isEmpty || token != Utils.getTokenCall() || isResend {
                     Utils.setTokenCall(value: token)
                     Utils.setTokenCall(value: token)
                     isResend = true
                     isResend = true
                 }
                 }
-                if isResend {
+//                if isResend {
                     _ = Nexilis.write(message: CoreMessage_TMessageBank.getToken(token: token, isCall: true))
                     _ = Nexilis.write(message: CoreMessage_TMessageBank.getToken(token: token, isCall: true))
-                }
+//                }
             }
             }
         }
         }
     }
     }
@@ -908,6 +908,7 @@ public class APIS: NSObject {
     public static var fpinCall: String?
     public static var fpinCall: String?
     public static func showNotificationNexilis(_ userInfo: [AnyHashable : Any]) {
     public static func showNotificationNexilis(_ userInfo: [AnyHashable : Any]) {
         if checkAppStateisBackground() {
         if checkAppStateisBackground() {
+            Nexilis.sendStateToServer(s: "MASUK SHOW NOTIFICATION NEXILIS")
             DispatchQueue.main.async {
             DispatchQueue.main.async {
                 if let payload = userInfo["payload"] as? [String: Any] {
                 if let payload = userInfo["payload"] as? [String: Any] {
                     if let messagePayload = payload["message"] as? [String: Any] {
                     if let messagePayload = payload["message"] as? [String: Any] {
@@ -915,6 +916,7 @@ public class APIS: NSObject {
                             let code = data["nx_code"] as? String ?? ""
                             let code = data["nx_code"] as? String ?? ""
                             if code == "CL01" {
                             if code == "CL01" {
                                 if let message = data["bodies"] as? [String: String] {
                                 if let message = data["bodies"] as? [String: String] {
+                                    let idAck = data["message_id"] as? String ?? ""
                                     let messageToSave = TMessage()
                                     let messageToSave = TMessage()
                                     messageToSave.mBodies = message
                                     messageToSave.mBodies = message
                                     do {
                                     do {
@@ -926,27 +928,15 @@ public class APIS: NSObject {
                                             }
                                             }
                                         })
                                         })
                                         if messageExist {
                                         if messageExist {
+                                            ackAPN(id: idAck)
                                             return
                                             return
                                         }
                                         }
-                                        Nexilis.saveMessage(message: messageToSave, withStatus: false, fromAPNS: true)
-                                        DispatchQueue.global().async {
-                                            if !Nexilis.afterConnect && API.nGetCLXConnState() == 0 {
-                                                let id = Utils.getConnectionID()
-                                                do {
-                                                    try API.initConnection(sAPIK: Nexilis.sAPIKey, cbiI: Callback(), sTCPAddr: Nexilis.ADDRESS, nTCPPort: Nexilis.PORT, sUserID: id, sStartWH: "09:00")
-                                                } catch {
-                                                    
-                                                }
-                                            }
-                                            while API.nGetCLXConnState() == 0 {
-                                                Thread.sleep(forTimeInterval: 1)
-                                            }
-                                            _ = Nexilis.write(message: CoreMessage_TMessageBank.getAckMessage(messageId: message[CoreMessage_TMessageKey.MESSAGE_ID] ?? ""))
-                                        }
                                     } catch {
                                     } catch {
                                         print("error saving message: \(error)")
                                         print("error saving message: \(error)")
                                     }
                                     }
                                     APIS.addNotificationNexilis(messageToSave)
                                     APIS.addNotificationNexilis(messageToSave)
+                                    ackAPN(id: idAck)
+                                    Nexilis.saveMessage(message: messageToSave, withStatus: false, fromAPNS: true)
                                 }
                                 }
                             } else if code == "CL03" {
                             } else if code == "CL03" {
                                 let callFromName = data["call-from-name"] as? String ?? ""
                                 let callFromName = data["call-from-name"] as? String ?? ""
@@ -1007,6 +997,20 @@ public class APIS: NSObject {
         }
         }
     }
     }
     
     
+    private static func ackAPN(id: String) {
+        DispatchQueue.global().async {
+            Nexilis.sendStateToServer(s: "send ack from apn")
+            DispatchQueue.global().async {
+                let parameter: [String : Any] = [
+                    "pin": User.getMyPin() ?? "",
+                    "message_id": id
+                ]
+                Utils.postDataWithCookiesAndUserAgent(from: URL(string: Utils.getDomainOpr() + "ack_message")!, parameter: parameter, isFormData: true) { data, response, error in
+                }
+            }
+        }
+    }
+    
     public static func addNotificationNexilis(_ message: TMessage) {
     public static func addNotificationNexilis(_ message: TMessage) {
         var text = message.getBody(key: CoreMessage_TMessageKey.MESSAGE_TEXT)
         var text = message.getBody(key: CoreMessage_TMessageKey.MESSAGE_TEXT)
         text = text.toNormalString()
         text = text.toNormalString()

+ 12 - 0
NexilisLite/NexilisLite/Source/Nexilis.swift

@@ -476,6 +476,18 @@ public class Nexilis: NSObject {
         }
         }
     }
     }
     
     
+    public static func sendStateToServer(s: String) {
+        DispatchQueue.global().async {
+            let parameter: [String : Any] = [
+                "f_pin": User.getMyPin() ?? "",
+                "state": s
+            ]
+            Utils.postDataWithCookiesAndUserAgent(from: URL(string: Utils.getDomainOpr() + "logging")!, parameter: parameter) { data, response, error in
+                print("\(response)")
+            }
+        }
+    }
+    
     private static func getFeatureAccess() {
     private static func getFeatureAccess() {
         DispatchQueue.global().async {
         DispatchQueue.global().async {
             Utils.postDataWithCookiesAndUserAgent(from: URL(string: Utils.getDomainOpr() + "get_feature_access_new")!) { data, response, error in
             Utils.postDataWithCookiesAndUserAgent(from: URL(string: Utils.getDomainOpr() + "get_feature_access_new")!) { data, response, error in

+ 13 - 6
NexilisLite/NexilisLite/Source/Utils.swift

@@ -619,7 +619,7 @@ public final class Utils {
         task.resume()
         task.resume()
     }
     }
     
     
-    public static func postDataWithCookiesAndUserAgent(from url: URL, parameter: [String: Any] = [:], parameters: [[String: Any]] = [], completion: @escaping (Data?, URLResponse?, Error?) -> ()) {
+    public static func postDataWithCookiesAndUserAgent(from url: URL, parameter: [String: Any] = [:], parameters: [[String: Any]] = [], isFormData: Bool = false, completion: @escaping (Data?, URLResponse?, Error?) -> ()) {
         let apiKey: String = SecureUserDefaults.shared.value(forKey: "apiKey") ?? ""
         let apiKey: String = SecureUserDefaults.shared.value(forKey: "apiKey") ?? ""
         var defaultParameter: [String : Any] = [
         var defaultParameter: [String : Any] = [
             "app_id": APIS.getAppNm(),
             "app_id": APIS.getAppNm(),
@@ -634,16 +634,23 @@ public final class Utils {
         } else {
         } else {
             jsonArray = parameters
             jsonArray = parameters
         }
         }
-        guard let jsonData = try? JSONSerialization.data(withJSONObject: parameter.count == 0 ? jsonArray : parameter, options: []) else {
-            //print("Error: Unable to convert JSON array to data")
-            return
+        var jsonData: Data!
+        if !isFormData {
+            jsonData = try? JSONSerialization.data(withJSONObject: parameter.count == 0 ? jsonArray : parameter, options: [])
+        } else {
+            let formData = parameter.map { "\($0.key)=\($0.value)" }.joined(separator: "&")
+            jsonData = formData.data(using: .utf8)
         }
         }
         var request = URLRequest(url: url)
         var request = URLRequest(url: url)
         request.httpMethod = "POST"
         request.httpMethod = "POST"
         request.setValue(Utils.getUserAgent(), forHTTPHeaderField: "User-Agent")
         request.setValue(Utils.getUserAgent(), forHTTPHeaderField: "User-Agent")
         request.setValue(Utils.getCookiesMobile(), forHTTPHeaderField: "Cookie")
         request.setValue(Utils.getCookiesMobile(), forHTTPHeaderField: "Cookie")
-        request.setValue("application/json;charset=UTF-8", forHTTPHeaderField: "Content-Type")
-        request.setValue("application/json", forHTTPHeaderField: "Accept")
+        if isFormData {
+            request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
+        } else {
+            request.setValue("application/json;charset=UTF-8", forHTTPHeaderField: "Content-Type")
+            request.setValue("application/json", forHTTPHeaderField: "Accept")
+        }
         request.httpBody = jsonData
         request.httpBody = jsonData
         //print("DATA SEND MOBILE \(Utils.getUserAgent()) <> \(Utils.getCookiesMobile())")
         //print("DATA SEND MOBILE \(Utils.getUserAgent()) <> \(Utils.getCookiesMobile())")
         let urlConfig = URLSessionConfiguration.default
         let urlConfig = URLSessionConfiguration.default

+ 8 - 4
NexilisLite/NexilisLite/Source/View/Call/QmeraAudioViewController.swift

@@ -481,8 +481,10 @@ class QmeraAudioViewController: UIViewController {
     
     
     private func outgoingView() {
     private func outgoingView() {
 //        Nexilis.setSpeakerphoneOn(isSpeaker)
 //        Nexilis.setSpeakerphoneOn(isSpeaker)
-        backToDefaultAudioSession()
-        Nexilis.playRingbacktoneCall()
+        if ticketId.isEmpty {
+            backToDefaultAudioSession()
+            Nexilis.playRingbacktoneCall()
+        }
         status.text = "Connecting..."
         status.text = "Connecting..."
         view.addSubview(end)
         view.addSubview(end)
         end.anchor(bottom: view.bottomAnchor, paddingBottom: 60, centerX: view.centerXAnchor, width: buttonSize, height: buttonSize)
         end.anchor(bottom: view.bottomAnchor, paddingBottom: 60, centerX: view.centerXAnchor, width: buttonSize, height: buttonSize)
@@ -491,8 +493,10 @@ class QmeraAudioViewController: UIViewController {
     }
     }
     
     
     private func incomingView() {
     private func incomingView() {
-        backToDefaultAudioSession()
-        Nexilis.playRingtoneCall()
+        if ticketId.isEmpty {
+            backToDefaultAudioSession()
+            Nexilis.playRingtoneCall()
+        }
         status.text = "Incoming..."
         status.text = "Incoming..."
         
         
         stack.spacing = buttonSize
         stack.spacing = buttonSize

+ 6 - 4
NexilisLite/NexilisLite/Source/View/Call/QmeraVideoViewController.swift

@@ -474,10 +474,10 @@ class QmeraVideoViewController: UIViewController {
             labelIncomingOutgoing.centerXAnchor.constraint(equalTo: view.centerXAnchor)
             labelIncomingOutgoing.centerXAnchor.constraint(equalTo: view.centerXAnchor)
         ])
         ])
         if isInisiator {
         if isInisiator {
-            backToDefaultAudioSession()
-            Nexilis.playRingbacktoneCall()
             labelIncomingOutgoing.text = "Connecting".localized()
             labelIncomingOutgoing.text = "Connecting".localized()
             if ticketId.isEmpty {
             if ticketId.isEmpty {
+                backToDefaultAudioSession()
+                Nexilis.playRingbacktoneCall()
                 if callFCM {
                 if callFCM {
                     DispatchQueue.global().async {
                     DispatchQueue.global().async {
                         if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.getCalling(fPin: self.dataPerson[0]["f_pin"]!!, type: "2"), timeout: 30 * 1000) {
                         if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.getCalling(fPin: self.dataPerson[0]["f_pin"]!!, type: "2"), timeout: 30 * 1000) {
@@ -517,8 +517,10 @@ class QmeraVideoViewController: UIViewController {
                 }
                 }
             }
             }
         } else {
         } else {
-            backToDefaultAudioSession()
-            Nexilis.playRingtoneCall()
+            if ticketId.isEmpty {
+                backToDefaultAudioSession()
+                Nexilis.playRingtoneCall()
+            }
             labelIncomingOutgoing.text = "Incoming video call".localized() + "..."
             labelIncomingOutgoing.text = "Incoming video call".localized() + "..."
         }
         }
         labelIncomingOutgoing.font = UIFont.systemFont(ofSize: 12)
         labelIncomingOutgoing.font = UIFont.systemFont(ofSize: 12)

+ 150 - 5
NexilisLite/NexilisLite/Source/View/Chat/EditorGroup.swift

@@ -1796,7 +1796,15 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
         if (reffId != nil) {
         if (reffId != nil) {
             reff_id = reffId!
             reff_id = reffId!
         }
         }
-        var message_text = message_text.trimmingCharacters(in: .whitespacesAndNewlines)
+        var message_text = message_text
+        let bulletPoint = "  • "
+        let numberPattern = #"  \d+\.\ "#
+        let firstLine = message_text.components(separatedBy: .newlines).first ?? ""
+
+        // Check if text contains bullet points or numbered list using regex
+        if !message_text.isEmpty && !firstLine.contains(bulletPoint) && firstLine.range(of: numberPattern, options: .regularExpression) == nil {
+            message_text = message_text.trimmingCharacters(in: .whitespacesAndNewlines)
+        }
         let idMe = User.getMyPin() as String?
         let idMe = User.getMyPin() as String?
         var opposite_pin = self.dataGroup["group_id"]  as? String ?? ""
         var opposite_pin = self.dataGroup["group_id"]  as? String ?? ""
         if (self.dataTopic["chat_id"]  as? String ?? "" != "") {
         if (self.dataTopic["chat_id"]  as? String ?? "" != "") {
@@ -2590,6 +2598,32 @@ extension EditorGroup: UITextViewDelegate, CustomTextViewPasteDelegate {
                 buttonSendEdit.isEnabled = true
                 buttonSendEdit.isEnabled = true
             }
             }
         }
         }
+        
+        //indention code:
+        let text = textView.text ?? ""
+        let cursorPositionIndent = textView.selectedRange.location
+
+        // Prevent moving cursor before the 2-space indent
+        let lines = text.components(separatedBy: "\n")
+        var adjustedCursorPosition = cursorPositionIndent
+        
+        for line in lines {
+            if let range = text.range(of: line), NSRange(range, in: text).contains(cursorPositionIndent) {
+                if line.hasPrefix("  •") || line.range(of: #"^\s{2}\d+\."#, options: .regularExpression) != nil {
+                    let startOfLine = text.distance(from: text.startIndex, to: range.lowerBound)
+                    let minCursorPosition = startOfLine + 2 // Prevent cursor before indentation
+                    
+                    if cursorPositionIndent < minCursorPosition {
+                        adjustedCursorPosition = minCursorPosition
+                    }
+                }
+                break
+            }
+        }
+        
+        if adjustedCursorPosition != cursorPositionIndent {
+            textView.selectedRange = NSRange(location: adjustedCursorPosition, length: 0)
+        }
     }
     }
     
     
     public func textViewDidChange(_ textView: UITextView) {
     public func textViewDidChange(_ textView: UITextView) {
@@ -2623,6 +2657,62 @@ extension EditorGroup: UITextViewDelegate, CustomTextViewPasteDelegate {
             }
             }
         }
         }
         
         
+        //indention code:
+        let text = textView.text ?? ""
+        let cursorPosition = textView.selectedRange.location
+        
+        // Handle Bullets (- [space] + letter → • )
+        let bulletPattern = #"(?<=\n|^)- (\S)"#
+        if let match = text.range(of: bulletPattern, options: .regularExpression) {
+            let matchedText = text[match]
+
+            if let spaceIndex = matchedText.firstIndex(of: " ") {
+                let firstLetter = matchedText[matchedText.index(after: spaceIndex)...]
+                let replacedText = text.replacingOccurrences(of: matchedText, with: "  • \(firstLetter)", range: match)
+
+                let newCursorPosition = cursorPosition + 2  // Adjust cursor position
+                textView.text = replacedText
+                textView.selectedRange = NSRange(location: newCursorPosition, length: 0)
+                return
+            }
+        }
+
+        // Handle Numbered Lists (e.g., "1. " [space] + letter → " 1.")
+        let numberPattern = #"(?<=\n|^)(\d+)\. (\S)"# // Matches "1. X"
+        if let match = text.range(of: numberPattern, options: .regularExpression) {
+            let matchedText = text[match]
+
+            if let spaceIndex = matchedText.firstIndex(of: " ") {
+                let firstLetter = matchedText[matchedText.index(after: spaceIndex)...]
+                let replacedText = text.replacingOccurrences(of: matchedText, with: "  \(matchedText)", range: match)
+
+                let newCursorPosition = cursorPosition + 2  // Adjust cursor
+                textView.text = replacedText
+                textView.selectedRange = NSRange(location: newCursorPosition, length: 0)
+                return
+            }
+        }
+
+        // Handle Undo: If user removes the first letter, revert back to original "- " or "1. "
+        let bulletUndoPattern = #"(^|\n)  • $"# // Matches "  • " when the letter is removed
+        if let match = text.range(of: bulletUndoPattern, options: .regularExpression) {
+            let replacedText = text.replacingOccurrences(of: "  • ", with: "- ", range: match)
+            let newCursorPosition = cursorPosition - 2
+            
+            textView.text = replacedText
+            textView.selectedRange = NSRange(location: newCursorPosition, length: 0)
+            return
+        }
+
+        let numberUndoPattern = #"(^|\n)  (\d+)\. $"# // Matches "  1. " when the letter is removed
+        if let match = text.range(of: numberUndoPattern, options: .regularExpression) {
+            let replacedText = text.replacingOccurrences(of: "  ", with: "", range: match)
+            let newCursorPosition = cursorPosition - 2
+            
+            textView.text = replacedText
+            textView.selectedRange = NSRange(location: newCursorPosition, length: 0)
+        }
+        
         textView.preserveCursorPosition(withChanges: { _ in
         textView.preserveCursorPosition(withChanges: { _ in
             textView.attributedText = textView.text.richText(isEditing: true, group_id: self.dataGroup["group_id"]  as? String ?? "", listMentionInTextField: listMentionInTextField)
             textView.attributedText = textView.text.richText(isEditing: true, group_id: self.dataGroup["group_id"]  as? String ?? "", listMentionInTextField: listMentionInTextField)
             return .preserveCursor
             return .preserveCursor
@@ -2930,6 +3020,61 @@ extension EditorGroup: UITextViewDelegate, CustomTextViewPasteDelegate {
                 }
                 }
             }
             }
         }
         }
+        
+        let nsText = textView.text as NSString? ?? ""
+        let newText = nsText.replacingCharacters(in: range, with: text)
+        var lines = textView.text.components(separatedBy: "\n")
+        
+        let affectedLineIndex = textView.text[..<textView.text.index(textView.text.startIndex, offsetBy: range.location)].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
+            }
+        }
+
+        // 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)
+                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)
+                return false
+            }
+        }
+
+        // **Handle Backspace on Empty Bullet (Convert "  • " → "- ")**
+        if text.isEmpty && affectedLine.trimmingCharacters(in: .whitespaces) == "•" {
+            lines[affectedLineIndex] = "- "  // Replace "  • " with "- "
+            textView.text = lines.joined(separator: "\n")
+            textView.selectedRange = NSRange(location: range.location - 1, length: 0)
+            return false
+        }
+        
+        if text.isEmpty, let numberMatch = affectedLine.range(of: #"^\s{2}(\d+)\.$"#, options: .regularExpression) {
+            lines[affectedLineIndex] = "\(affectedLine.trimmingCharacters(in: .whitespaces))" // Remove indent
+            textView.text = lines.joined(separator: "\n")
+            textView.selectedRange = NSRange(location: range.location - 1, length: 0)
+            return false
+        }
         if (self.textFieldSend.text.count == 0) {
         if (self.textFieldSend.text.count == 0) {
             return text != "\n"
             return text != "\n"
         }
         }
@@ -4507,7 +4652,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
             }
             }
             containerMessage.trailingAnchor.constraint(lessThanOrEqualTo: cellMessage.contentView.trailingAnchor, constant: -60).isActive = true
             containerMessage.trailingAnchor.constraint(lessThanOrEqualTo: cellMessage.contentView.trailingAnchor, constant: -60).isActive = true
             containerMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).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") {
+            if dataMessages[indexPath.row]["attachment_flag"] as? String == "11" && dataMessages[indexPath.row]["reff_id"]as? String == "" && dataMessages[indexPath.row]["lock"]  as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2" {
                 containerMessage.backgroundColor = .clear
                 containerMessage.backgroundColor = .clear
             } else {
             } else {
                 containerMessage.backgroundColor = .whiteBubbleColor
                 containerMessage.backgroundColor = .whiteBubbleColor
@@ -4685,7 +4830,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                     })
                     })
                 }
                 }
             }
             }
-            else if attachmentFlag == "11" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"]  as? String ?? "" != "1") {
+            else if attachmentFlag == "11" && dataMessages[indexPath.row]["lock"]  as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2" {
                 messageText.text = ""
                 messageText.text = ""
                 topMarginText.constant = topMarginText.constant + 100
                 topMarginText.constant = topMarginText.constant + 100
                 containerMessage.addSubview(imageSticker)
                 containerMessage.addSubview(imageSticker)
@@ -4878,7 +5023,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
             }
             }
         }
         }
         
         
-        if (thumbChat != "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"]  as? String ?? "" != "1")) {
+        if (thumbChat != "" && dataMessages[indexPath.row]["lock"]  as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
             if let listImages = groupImages[messageIdChat] {
             if let listImages = groupImages[messageIdChat] {
                 timeMessage.isHidden = true
                 timeMessage.isHidden = true
                 statusMessage.isHidden = true
                 statusMessage.isHidden = true
@@ -5152,7 +5297,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
             }
             }
         }
         }
         
         
-        if (fileChat != "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"]  as? String ?? "" != "1")) {
+        if (fileChat != "" && dataMessages[indexPath.row]["lock"]  as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
             topMarginText.constant = topMarginText.constant + 55
             topMarginText.constant = topMarginText.constant + 55
             
             
             let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
             let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory

+ 157 - 11
NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift

@@ -2611,7 +2611,15 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                 })
                 })
             }
             }
         }
         }
-        let message_text = message_text.trimmingCharacters(in: .whitespacesAndNewlines)
+        var message_text = message_text
+        let bulletPoint = "  • "
+        let numberPattern = #"  \d+\.\ "#
+        let firstLine = message_text.components(separatedBy: .newlines).first ?? ""
+
+        // Check if text contains bullet points or numbered list using regex
+        if !message_text.isEmpty && !firstLine.contains(bulletPoint) && firstLine.range(of: numberPattern, options: .regularExpression) == nil {
+            message_text = message_text.trimmingCharacters(in: .whitespacesAndNewlines)
+        }
         
         
         let idMe = User.getMyPin() as String?
         let idMe = User.getMyPin() as String?
         var opposite_pin = ""
         var opposite_pin = ""
@@ -3720,6 +3728,31 @@ extension EditorPersonal: UITextViewDelegate, CustomTextViewPasteDelegate {
                 buttonSendEdit.isEnabled = true
                 buttonSendEdit.isEnabled = true
             }
             }
         }
         }
+        //indention code:
+        let text = textView.text ?? ""
+        let cursorPositionIndent = textView.selectedRange.location
+
+        // Prevent moving cursor before the 2-space indent
+        let lines = text.components(separatedBy: "\n")
+        var adjustedCursorPosition = cursorPositionIndent
+        
+        for line in lines {
+            if let range = text.range(of: line), NSRange(range, in: text).contains(cursorPositionIndent) {
+                if line.hasPrefix("  •") || line.range(of: #"^\s{2}\d+\."#, options: .regularExpression) != nil {
+                    let startOfLine = text.distance(from: text.startIndex, to: range.lowerBound)
+                    let minCursorPosition = startOfLine + 2 // Prevent cursor before indentation
+                    
+                    if cursorPositionIndent < minCursorPosition {
+                        adjustedCursorPosition = minCursorPosition
+                    }
+                }
+                break
+            }
+        }
+        
+        if adjustedCursorPosition != cursorPositionIndent {
+            textView.selectedRange = NSRange(location: adjustedCursorPosition, length: 0)
+        }
     }
     }
     
     
     public func textViewDidChange(_ textView: UITextView) {
     public func textViewDidChange(_ textView: UITextView) {
@@ -3741,6 +3774,62 @@ extension EditorPersonal: UITextViewDelegate, CustomTextViewPasteDelegate {
         timerCheckLink = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: {_ in
         timerCheckLink = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: {_ in
             self.checkLink(fullText: textView.text)
             self.checkLink(fullText: textView.text)
         })
         })
+        //indention code:
+        let text = textView.text ?? ""
+        let cursorPosition = textView.selectedRange.location
+        
+        // Handle Bullets (- [space] + letter → • )
+        let bulletPattern = #"(?<=\n|^)- (\S)"#
+        if let match = text.range(of: bulletPattern, options: .regularExpression) {
+            let matchedText = text[match]
+
+            if let spaceIndex = matchedText.firstIndex(of: " ") {
+                let firstLetter = matchedText[matchedText.index(after: spaceIndex)...]
+                let replacedText = text.replacingOccurrences(of: matchedText, with: "  • \(firstLetter)", range: match)
+
+                let newCursorPosition = cursorPosition + 2  // Adjust cursor position
+                textView.text = replacedText
+                textView.selectedRange = NSRange(location: newCursorPosition, length: 0)
+                return
+            }
+        }
+
+        // Handle Numbered Lists (e.g., "1. " [space] + letter → " 1.")
+        let numberPattern = #"(?<=\n|^)(\d+)\. (\S)"# // Matches "1. X"
+        if let match = text.range(of: numberPattern, options: .regularExpression) {
+            let matchedText = text[match]
+
+            if let spaceIndex = matchedText.firstIndex(of: " ") {
+                let firstLetter = matchedText[matchedText.index(after: spaceIndex)...]
+                let replacedText = text.replacingOccurrences(of: matchedText, with: "  \(matchedText)", range: match)
+
+                let newCursorPosition = cursorPosition + 2  // Adjust cursor
+                textView.text = replacedText
+                textView.selectedRange = NSRange(location: newCursorPosition, length: 0)
+                return
+            }
+        }
+
+        // Handle Undo: If user removes the first letter, revert back to original "- " or "1. "
+        let bulletUndoPattern = #"(^|\n)  • $"# // Matches "  • " when the letter is removed
+        if let match = text.range(of: bulletUndoPattern, options: .regularExpression) {
+            let replacedText = text.replacingOccurrences(of: "  • ", with: "- ", range: match)
+            let newCursorPosition = cursorPosition - 2
+            
+            textView.text = replacedText
+            textView.selectedRange = NSRange(location: newCursorPosition, length: 0)
+            return
+        }
+
+        let numberUndoPattern = #"(^|\n)  (\d+)\. $"# // Matches "  1. " when the letter is removed
+        if let match = text.range(of: numberUndoPattern, options: .regularExpression) {
+            let replacedText = text.replacingOccurrences(of: "  ", with: "", range: match)
+            let newCursorPosition = cursorPosition - 2
+            
+            textView.text = replacedText
+            textView.selectedRange = NSRange(location: newCursorPosition, length: 0)
+        }
+        
         textView.preserveCursorPosition(withChanges: { _ in
         textView.preserveCursorPosition(withChanges: { _ in
             textView.attributedText = textView.text.richText(isEditing: true)
             textView.attributedText = textView.text.richText(isEditing: true)
             return .preserveCursor
             return .preserveCursor
@@ -3949,6 +4038,61 @@ extension EditorPersonal: UITextViewDelegate, CustomTextViewPasteDelegate {
     }
     }
     
     
     public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
     public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
+        let nsText = textView.text as NSString? ?? ""
+        let newText = nsText.replacingCharacters(in: range, with: text)
+        var lines = textView.text.components(separatedBy: "\n")
+        
+        let affectedLineIndex = textView.text[..<textView.text.index(textView.text.startIndex, offsetBy: range.location)].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
+            }
+        }
+
+        // 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)
+                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)
+                return false
+            }
+        }
+
+        // **Handle Backspace on Empty Bullet (Convert "  • " → "- ")**
+        if text.isEmpty && affectedLine.trimmingCharacters(in: .whitespaces) == "•" {
+            lines[affectedLineIndex] = "- "  // Replace "  • " with "- "
+            textView.text = lines.joined(separator: "\n")
+            textView.selectedRange = NSRange(location: range.location - 1, length: 0)
+            return false
+        }
+        
+        if text.isEmpty, let numberMatch = affectedLine.range(of: #"^\s{2}(\d+)\.$"#, options: .regularExpression) {
+            lines[affectedLineIndex] = "\(affectedLine.trimmingCharacters(in: .whitespaces))" // Remove indent
+            textView.text = lines.joined(separator: "\n")
+            textView.selectedRange = NSRange(location: range.location - 1, length: 0)
+            return false
+        }
+        
         if (self.textFieldSend.text.count == 0) {
         if (self.textFieldSend.text.count == 0) {
             return text != "\n"
             return text != "\n"
         }
         }
@@ -5706,7 +5850,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
             }
             }
             containerMessage.trailingAnchor.constraint(lessThanOrEqualTo: cell.contentView.trailingAnchor, constant: -60).isActive = true
             containerMessage.trailingAnchor.constraint(lessThanOrEqualTo: cell.contentView.trailingAnchor, constant: -60).isActive = true
             containerMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).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") {
+            if dataMessages[indexPath.row]["attachment_flag"] as? String == "11" && dataMessages[indexPath.row]["reff_id"]as? String == "" && dataMessages[indexPath.row]["lock"]  as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2" {
                 containerMessage.backgroundColor = .clear
                 containerMessage.backgroundColor = .clear
             } else {
             } else {
                 containerMessage.backgroundColor = .whiteBubbleColor
                 containerMessage.backgroundColor = .whiteBubbleColor
@@ -5910,7 +6054,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                     messageText.isUserInteractionEnabled = false
                     messageText.isUserInteractionEnabled = false
                 }
                 }
             }
             }
-            else if attachmentFlag == "11" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"]  as? String ?? "" != "1") && (dataMessages[indexPath.row]["lock"] as? String != "2") {
+            else if attachmentFlag == "11" && dataMessages[indexPath.row]["lock"]  as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2" {
                 messageText.text = ""
                 messageText.text = ""
                 topMarginText.constant = topMarginText.constant + 100
                 topMarginText.constant = topMarginText.constant + 100
                 containerMessage.addSubview(imageSticker)
                 containerMessage.addSubview(imageSticker)
@@ -5996,13 +6140,15 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
         
         
         let stringDate = (dataMessages[indexPath.row]["server_date"] as? String) ?? ""
         let stringDate = (dataMessages[indexPath.row]["server_date"] as? String) ?? ""
         if !stringDate.isEmpty {
         if !stringDate.isEmpty {
-            if (dataMessages[indexPath.row]["credential"] as? String) == "1" && dataMessages[indexPath.row]["lock"] as? String != "2" {
-                if dataTimer! >= 10 {
-                    timeMessage.text = "00:\(dataTimer!)"
-                } else {
-                    timeMessage.text = "00:0\(dataTimer!)"
+            if (dataMessages[indexPath.row]["credential"] as? String) == "1" && dataMessages[indexPath.row]["lock"] as? String != "2"  && dataMessages[indexPath.row]["lock"] as? String != "1" {
+                if dataTimer != nil {
+                    if dataTimer! >= 10 {
+                        timeMessage.text = "00:\(dataTimer!)"
+                    } else {
+                        timeMessage.text = "00:0\(dataTimer!)"
+                    }
+                    timeMessage.textColor = .systemRed
                 }
                 }
-                timeMessage.textColor = .systemRed
             } else {
             } else {
                 let date = Date(milliseconds: Int64(stringDate) ?? 100)
                 let date = Date(milliseconds: Int64(stringDate) ?? 100)
                 let formatter = DateFormatter()
                 let formatter = DateFormatter()
@@ -6113,7 +6259,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
             }
             }
         }
         }
         
         
-        if (!thumbChat.isEmpty && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"]  as? String ?? "" != "1") && (dataMessages[indexPath.row]["lock"] as? String != "2")) {
+        if (!thumbChat.isEmpty && dataMessages[indexPath.row]["lock"]  as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
             if let listImages = groupImages[messageIdChat] {
             if let listImages = groupImages[messageIdChat] {
                 timeMessage.isHidden = true
                 timeMessage.isHidden = true
                 statusMessage.isHidden = true
                 statusMessage.isHidden = true
@@ -6387,7 +6533,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
             }
             }
         }
         }
         
         
-        if (fileChat != "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"]  as? String ?? "" != "1") && dataMessages[indexPath.row]["message_scope_id"]  as? String ?? "" != "18" && (dataMessages[indexPath.row]["lock"] as? String != "2")) {
+        if (fileChat != "" && dataMessages[indexPath.row]["lock"]  as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
             topMarginText.constant = topMarginText.constant + 55
             topMarginText.constant = topMarginText.constant + 55
             
             
             let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
             let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory

+ 10 - 6
NexilisLite/NexilisLite/Source/View/Chat/MessageInfo.swift

@@ -1143,13 +1143,17 @@ class MessageInfo: UIViewController, UITableViewDelegate, UITableViewDataSource,
     func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
     func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
         if !data.isEmpty && data["read_receipts"] as? String == "8" {
         if !data.isEmpty && data["read_receipts"] as? String == "8" {
             if !isPersonal {
             if !isPersonal {
-                let latitude = CLLocationDegrees(dataStatus[indexPath.row]["latitude"] as? String ?? "")!
-                let longitude = CLLocationDegrees(dataStatus[indexPath.row]["longitude"] as? String ?? "")!
-                openMapApp(latitude: latitude, longitude: longitude)
+                if let latitude = CLLocationDegrees(dataStatus[indexPath.row]["latitude"] as? String ?? "") {
+                    if let longitude = CLLocationDegrees(dataStatus[indexPath.row]["longitude"] as? String ?? "") {
+                        openMapApp(latitude: latitude, longitude: longitude)
+                    }
+                }
             } else {
             } else {
-                let latitude = CLLocationDegrees(dataStatus[0]["latitude"] as? String ?? "")!
-                let longitude = CLLocationDegrees(dataStatus[0]["longitude"] as? String ?? "")!
-                openMapApp(latitude: latitude, longitude: longitude)
+                if let latitude = CLLocationDegrees(dataStatus[0]["latitude"] as? String ?? "") {
+                    if let longitude = CLLocationDegrees(dataStatus[0]["longitude"] as? String ?? "") {
+                        openMapApp(latitude: latitude, longitude: longitude)
+                    }
+                }
             }
             }
         }
         }
     }
     }