Jelajahi Sumber

update fix bugs and release for 5.0.50

alqindiirsyam 1 bulan lalu
induk
melakukan
215734c8fb

+ 4 - 4
AppBuilder/AppBuilder.xcodeproj/project.pbxproj

@@ -564,7 +564,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 5.0.48;
+				MARKETING_VERSION = 5.0.50;
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
@@ -600,7 +600,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 5.0.48;
+				MARKETING_VERSION = 5.0.50;
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
@@ -636,7 +636,7 @@
 					"@executable_path/../../Frameworks",
 				);
 				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
-				MARKETING_VERSION = 5.0.48;
+				MARKETING_VERSION = 5.0.50;
 				OTHER_CFLAGS = "-fstack-protector-strong";
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder.AppBuilderShare;
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -675,7 +675,7 @@
 					"@executable_path/../../Frameworks",
 				);
 				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
-				MARKETING_VERSION = 5.0.48;
+				MARKETING_VERSION = 5.0.50;
 				OTHER_CFLAGS = "-fstack-protector-strong";
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder.AppBuilderShare;
 				PRODUCT_NAME = "$(TARGET_NAME)";

+ 6 - 6
AppBuilder/AppBuilder/FirstTabViewController.swift

@@ -207,8 +207,8 @@ class FirstTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
     override func viewDidAppear(_ animated: Bool) {
         DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: {
             var viewController = UIApplication.shared.windows.first!.rootViewController
-            if !(viewController is ViewController) {
-                viewController = self.parent
+            if let nc = viewController as? UINavigationController {
+                viewController = nc.viewControllers.first
             }
             if ViewController.middleButton.isHidden {
                 ViewController.isExpandButton = false
@@ -288,8 +288,8 @@ class FirstTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
             return
         }
         var viewController = UIApplication.shared.windows.first!.rootViewController
-        if !(viewController is ViewController) {
-            viewController = self.parent
+        if let nc = viewController as? UINavigationController {
+            viewController = nc.viewControllers.first
         }
         if ViewController.middleButton.isDescendant(of: viewController!.view) {
             DispatchQueue.main.async {
@@ -318,8 +318,8 @@ class FirstTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
             return
         }
         var viewController = UIApplication.shared.windows.first!.rootViewController
-        if !(viewController is ViewController) {
-            viewController = self.parent
+        if let nc = viewController as? UINavigationController {
+            viewController = nc.viewControllers.first
         }
         if ViewController.middleButton.isHidden {
             if let viewController = viewController as? ViewController {

+ 6 - 4
AppBuilder/AppBuilder/FourthTabViewController.swift

@@ -107,8 +107,8 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
         self.navigationController?.navigationBar.setNeedsLayout()
         DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: {
             var viewController = UIApplication.shared.windows.first!.rootViewController
-            if !(viewController is ViewController) {
-                viewController = self.parent
+            if let nc = viewController as? UINavigationController {
+                viewController = nc.viewControllers.first
             }
             if ViewController.middleButton.isHidden {
                 ViewController.isExpandButton = false
@@ -309,6 +309,7 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
         backgroundImage.anchor(top: self.view.topAnchor, left: self.view.leftAnchor, bottom: self.view.bottomAnchor, right: self.view.rightAnchor)
         
         tableView = UITableView()
+        tableView.backgroundColor = .clear
         tableView.register(UITableViewCell.self, forCellReuseIdentifier: "reuseIdentifier")
         self.view.addSubview(tableView)
         tableView.anchor(top: self.view.safeAreaLayoutGuide.topAnchor, left: self.view.leftAnchor, bottom: self.view.bottomAnchor, right: self.view.rightAnchor)
@@ -486,6 +487,7 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
         let isChangeProfile = Utils.getSetProfile()
         cell.accessoryType = .none
         cell.indentationLevel = 0
+        cell.backgroundColor = .clear
         var content = cell.defaultContentConfiguration()
         content.textProperties.font = UIFont.systemFont(ofSize: 14)
         content.secondaryTextProperties.font = UIFont.systemFont(ofSize: 14)
@@ -730,8 +732,8 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
             alert.addAction(UIAlertAction(title: "Cancel".localized(), style: UIAlertAction.Style.default, handler: nil))
             alert.addAction(UIAlertAction(title: "Yes".localized(), style: .destructive, handler: {(_) in
                 var viewController = UIApplication.shared.windows.first!.rootViewController
-                if !(viewController is ViewController) {
-                    viewController = self.parent
+                if let nc = viewController as? UINavigationController {
+                    viewController = nc.viewControllers.first
                 }
                 if let viewController = viewController as? ViewController {
                     if !(viewController.selectedViewController is FourthTabViewController) {

+ 4 - 4
AppBuilder/AppBuilder/SecondTabViewController.swift

@@ -569,8 +569,8 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
 //        self.navigationController?.navigationBar.setNeedsLayout()
         DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: {
             var viewController = UIApplication.shared.windows.first!.rootViewController
-            if !(viewController is ViewController) {
-                viewController = self.parent
+            if let nc = viewController as? UINavigationController {
+                viewController = nc.viewControllers.first
             }
             if ViewController.middleButton.isHidden {
                 ViewController.isExpandButton = false
@@ -1380,8 +1380,8 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
                     cursorMember.close()
                 } else {
                     var viewController = UIApplication.shared.windows.first!.rootViewController
-                    if !(viewController is ViewController) {
-                        viewController = self.parent
+                    if let nc = viewController as? UINavigationController {
+                        viewController = nc.viewControllers.first
                     }
                     if let viewController = viewController as? ViewController {
                         viewController.view.makeToast("You are not a member of this group".localized(), duration: 3)

+ 19 - 19
AppBuilder/AppBuilder/ThirdTabViewController.swift

@@ -209,29 +209,29 @@ class ThirdTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
     
     override func viewDidAppear(_ animated: Bool) {
         DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: {
-                var viewController = UIApplication.shared.windows.first!.rootViewController
-                if !(viewController is ViewController) {
-                    viewController = self.parent
+            var viewController = UIApplication.shared.windows.first!.rootViewController
+            if let nc = viewController as? UINavigationController {
+                viewController = nc.viewControllers.first
+            }
+            if ViewController.middleButton.isHidden {
+                ViewController.isExpandButton = false
+                if let viewController = viewController as? ViewController {
+                    if viewController.tabBar.isHidden {
+                        viewController.tabBar.isHidden = false
+                        ViewController.middleButton.isHidden = false
+                        ViewController.alwaysHideButton = false
+                    }
                 }
-                if ViewController.middleButton.isHidden {
-                    ViewController.isExpandButton = false
+            } else if PrefsUtil.getCpaasMode() != PrefsUtil.CPAAS_MODE_DOCKED {
+                DispatchQueue.main.async {
                     if let viewController = viewController as? ViewController {
                         if viewController.tabBar.isHidden {
                             viewController.tabBar.isHidden = false
-                            ViewController.middleButton.isHidden = false
                             ViewController.alwaysHideButton = false
                         }
                     }
-                } else if PrefsUtil.getCpaasMode() != PrefsUtil.CPAAS_MODE_DOCKED {
-                    DispatchQueue.main.async {
-                        if let viewController = viewController as? ViewController {
-                            if viewController.tabBar.isHidden {
-                                viewController.tabBar.isHidden = false
-                                ViewController.alwaysHideButton = false
-                            }
-                        }
-                    }
                 }
+            }
 //            }
         })
     }
@@ -295,8 +295,8 @@ class ThirdTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
             return
         }
         var viewController = UIApplication.shared.windows.first!.rootViewController
-        if !(viewController is ViewController) {
-            viewController = self.parent
+        if let nc = viewController as? UINavigationController {
+            viewController = nc.viewControllers.first
         }
         if ViewController.middleButton.isDescendant(of: viewController!.view) {
             DispatchQueue.main.async {
@@ -325,8 +325,8 @@ class ThirdTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
             return
         }
         var viewController = UIApplication.shared.windows.first!.rootViewController
-        if !(viewController is ViewController) {
-            viewController = self.parent
+        if let nc = viewController as? UINavigationController {
+            viewController = nc.viewControllers.first
         }
         if ViewController.middleButton.isHidden {
             if let viewController = viewController as? ViewController {

+ 31 - 23
NexilisLite/NexilisLite/Source/InquiryThread.swift

@@ -22,33 +22,38 @@ class InquiryThread {
     private var queue = [TMessage]()
     
     init() {
-        Database.shared.database?.inTransaction({ (fmdb, rollback) in
-            do {
-                if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select status, message, id from INQUIRY") {
-                    while cursor.next() {
-                        let status = cursor.int(forColumnIndex: 0)
-                        if status == 1 {
-                            delInquiry(fmdb: fmdb, messageId: cursor.string(forColumnIndex: 2)!)
-                            continue
-                        }
-                        if let cursorMessage = Database.shared.getRecords(fmdb: fmdb, query: "select message_id from MESSAGE where message_id = '\(cursor.string(forColumnIndex: 2)!)'") {
-                            if cursorMessage.next() {
-                                if let message = cursor.string(forColumnIndex: 1) {
-                                    addQueue(message: TMessage(data: message))
-                                }
-                            } else {
+        DispatchQueue.global().async { [self] in
+            while Database.shared.database == nil {
+                Thread.sleep(forTimeInterval: 1.0)
+            }
+            Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                do {
+                    if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select status, message, id from INQUIRY") {
+                        while cursor.next() {
+                            let status = cursor.int(forColumnIndex: 0)
+                            if status == 1 {
                                 delInquiry(fmdb: fmdb, messageId: cursor.string(forColumnIndex: 2)!)
+                                continue
+                            }
+                            if let cursorMessage = Database.shared.getRecords(fmdb: fmdb, query: "select message_id from MESSAGE where message_id = '\(cursor.string(forColumnIndex: 2)!)'") {
+                                if cursorMessage.next() {
+                                    if let message = cursor.string(forColumnIndex: 1) {
+                                        addQueue(message: TMessage(data: message))
+                                    }
+                                } else {
+                                    delInquiry(fmdb: fmdb, messageId: cursor.string(forColumnIndex: 2)!)
+                                }
+                                cursorMessage.close()
                             }
-                            cursorMessage.close()
                         }
+                        cursor.close()
                     }
-                    cursor.close()
+                } catch {
+                    rollback.pointee = true
+                    print("Access database error: \(error.localizedDescription)")
                 }
-            } catch {
-                rollback.pointee = true
-                print("Access database error: \(error.localizedDescription)")
-            }
-        })
+            })
+        }
     }
     
     private func delInquiry(fmdb: Any, messageId: String) {
@@ -142,7 +147,10 @@ class InquiryThread {
         //print("save message sendChat")
         if !self.isWait {
             //print("inquiry write", message.toLogString())
-            _ = Nexilis.write(message: CoreMessage_TMessageBank.getMobileInquiry(message_id: message.getBody(key: CoreMessage_TMessageKey.MESSAGE_ID)))
+            var res = Nexilis.write(message: CoreMessage_TMessageBank.getMobileInquiry(message_id: message.getBody(key: CoreMessage_TMessageKey.MESSAGE_ID)))
+            while res == nil {
+                res = Nexilis.write(message: CoreMessage_TMessageBank.getMobileInquiry(message_id: message.getBody(key: CoreMessage_TMessageKey.MESSAGE_ID)))
+            }
         } else {
             queue.append(message)
         }

+ 6 - 6
NexilisLite/NexilisLite/Source/Nexilis.swift

@@ -19,7 +19,7 @@ import CryptoKit
 import WebKit
 
 public class Nexilis: NSObject {
-    public static var cpaasVersion = "5.0.48"
+    public static var cpaasVersion = "5.0.50"
     public static var sAPIKey = ""
     
     public static var ADDRESS = ""
@@ -1109,7 +1109,7 @@ public class Nexilis: NSObject {
     
     public static var isProcessWriteSync = false
     public static func writeSync(message: TMessage, timeout: Int = 15 * 1000) -> TMessage? {
-        if !API.bInetConnAvailable() {
+        if !API.bInetConnAvailable() || API.nGetCLXConnState() == 0 {
             return nil
         }
         while Nexilis.isProcessWriteSync {
@@ -1132,7 +1132,7 @@ public class Nexilis: NSObject {
     
     public static func write(message: TMessage, timeout: Int = 15 * 1000) -> String? {
         do {
-            if !API.bInetConnAvailable() {
+            if !API.bInetConnAvailable() || API.nGetCLXConnState() == 0 {
                 return nil
             }
             //print(">> SENDING MESSAGE >> ", message.toLogString())
@@ -1155,7 +1155,7 @@ public class Nexilis: NSObject {
     
     public static func writeDraw(data: String, timeout: Int = 15 * 1000) -> String? {
         do {
-            if !API.bInetConnAvailable() {
+            if !API.bInetConnAvailable() || API.nGetCLXConnState() == 0 {
                 return nil
             }
             //print(">> SENDING MESSAGE >> ", data)
@@ -1172,7 +1172,7 @@ public class Nexilis: NSObject {
     public static func response(packetId: String, message: TMessage, timeout: Int = 15 * 1000) -> String? {
         var result: String? = nil
         do {
-            if !API.bInetConnAvailable() {
+            if !API.bInetConnAvailable() || API.nGetCLXConnState() == 0 {
                 return nil
             }
             //print(">> RESPONSE >> " + packetId + " " + message.toLogString());
@@ -1186,7 +1186,7 @@ public class Nexilis: NSObject {
     public static func responseString(packetId: String, message: String, timeout: Int = 15 * 1000) -> String? {
         var result: String? = nil
         do {
-            if !API.bInetConnAvailable() {
+            if !API.bInetConnAvailable() || API.nGetCLXConnState() == 0 {
                 return nil
             }
             //print(">> RESPONSE >> " + packetId + " " + message);

+ 22 - 17
NexilisLite/NexilisLite/Source/OutgoingThread.swift

@@ -24,30 +24,35 @@ class OutgoingThread {
     private var queue = [TMessage]()
     
     init() {
-        Database.shared.database?.inTransaction({ (fmdb, rollback) in
-            do {
-                if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select message, id from OUTGOING") {
-                    while cursor.next() {
-                        if let message = cursor.string(forColumnIndex: 0) {
-                            if let cursorMessage = Database.shared.getRecords(fmdb: fmdb, query: "select message_id from MESSAGE where message_id = '\(cursor.string(forColumnIndex: 1)!)'") {
-                                if cursorMessage.next() {
-                                    addQueue(message: TMessage(data: message))
+        DispatchQueue.global().async { [self] in
+            while Database.shared.database == nil {
+                Thread.sleep(forTimeInterval: 1.0)
+            }
+            Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                do {
+                    if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select message, id from OUTGOING") {
+                        while cursor.next() {
+                            if let message = cursor.string(forColumnIndex: 0) {
+                                if let cursorMessage = Database.shared.getRecords(fmdb: fmdb, query: "select message_id from MESSAGE where message_id = '\(cursor.string(forColumnIndex: 1)!)'") {
+                                    if cursorMessage.next() {
+                                        addQueue(message: TMessage(data: message))
+                                    } else {
+                                        delOutgoing(fmdb: fmdb, messageId: cursor.string(forColumnIndex: 1)!)
+                                    }
+                                    cursorMessage.close()
                                 } else {
                                     delOutgoing(fmdb: fmdb, messageId: cursor.string(forColumnIndex: 1)!)
                                 }
-                                cursorMessage.close()
-                            } else {
-                                delOutgoing(fmdb: fmdb, messageId: cursor.string(forColumnIndex: 1)!)
                             }
                         }
+                        cursor.close()
                     }
-                    cursor.close()
+                } catch {
+                    rollback.pointee = true
+                    print("Access database error: \(error.localizedDescription)")
                 }
-            } catch {
-                rollback.pointee = true
-                print("Access database error: \(error.localizedDescription)")
-            }
-        })
+            })
+        }
     }
     
     func addQueue(message: TMessage) {

+ 72 - 44
NexilisLite/NexilisLite/Source/View/Chat/EditorGroup.swift

@@ -2180,23 +2180,49 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
             return
         }
         DispatchQueue.global().async {
-            while API.nGetCLXConnState() == 0 || !API.bInetConnAvailable() {
-                Thread.sleep(forTimeInterval: 1)
-            }
-            Database.shared.database?.inTransaction({ (fmdb, rollback) in
-                do {
-                    _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
-                        "status" : "4"
-                    ], _where: "message_id = '\(message_id)'")
-                } catch {
-                    rollback.pointee = true
-                    print("Access database error: \(error.localizedDescription)")
+            var isBackground = true
+            while isBackground {
+                DispatchQueue.main.sync {
+                    isBackground = API.nGetCLXConnState() == 0 || !API.bInetConnAvailable() || APIS.checkAppStateisBackground()
                 }
-            })
-            message.mStatus = CoreMessage_TMessageUtil.getTID()
-            message.mBodies[CoreMessage_TMessageKey.L_PIN] = f_pin
-            message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID] = "-2,\(message_id)"
-            _ = Nexilis.write(message: message)
+                if isBackground {
+                    Thread.sleep(forTimeInterval: 1.0)
+                } else {
+                    if let listGroupImages = self.groupImages.first(where: { $0.key == message_id }) {
+                        let valueListGroupImages = listGroupImages.value
+                        for i in 0..<valueListGroupImages.count {
+                            Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                                do {
+                                    _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
+                                        "status" : "4"
+                                    ], _where: "message_id = '\(valueListGroupImages[i].messageId)'")
+                                } catch {
+                                    rollback.pointee = true
+                                    print("Access database error: \(error.localizedDescription)")
+                                }
+                            })
+                            message.mStatus = CoreMessage_TMessageUtil.getTID()
+                            message.mBodies[CoreMessage_TMessageKey.L_PIN] = f_pin
+                            message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID] = "-2,\(valueListGroupImages[i].messageId)"
+                        }
+                    } else {
+                        Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                            do {
+                                _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
+                                    "status" : "4"
+                                ], _where: "message_id = '\(message_id)'")
+                            } catch {
+                                rollback.pointee = true
+                                print("Access database error: \(error.localizedDescription)")
+                            }
+                        })
+                        message.mStatus = CoreMessage_TMessageUtil.getTID()
+                        message.mBodies[CoreMessage_TMessageKey.L_PIN] = f_pin
+                        message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID] = "-2,\(message_id)"
+                    }
+                    _ = Nexilis.write(message: message)
+                }
+            }
         }
         if let index = dataMessages.firstIndex(where: {$0["message_id"] as? String == message_id}) {
             dataMessages[index]["status"] = "4"
@@ -7385,36 +7411,38 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
         obj.message_id = dataMessagesPin[nextPinShowed][TypeDataMessage.message_id] as? String ?? ""
         contentMessageTapped(obj)
         
-        if nextPinShowed < dataMessagesPin.count - 1 {
-            nextPinShowed+=1
-        } else {
-            nextPinShowed = 0
-        }
-        
-        DispatchQueue.main.async {
-            self.signSelectedPin.subviews.forEach({ $0.removeFromSuperview() })
-            self.signSelectedPin.removeFromSuperview()
-            self.containerPin.addSubview(self.signSelectedPin)
-            self.signSelectedPin.anchor(left: self.containerPin.leftAnchor, paddingLeft: 8, centerY: self.containerPin.centerYAnchor, width: 2, height: 30)
-            self.signSelectedPin.layer.cornerRadius = 1
-            self.signSelectedPin.clipsToBounds = true
-            self.signSelectedPin.alignment = .fill
-            self.signSelectedPin.axis = .vertical
-            self.signSelectedPin.distribution = .fill
-            self.signSelectedPin.spacing = dataMessagesPin.count == 3 ? 1.5 : 2
+        if dataMessagesPin.count > 0 {
+            if nextPinShowed < dataMessagesPin.count - 1 {
+                nextPinShowed+=1
+            } else {
+                nextPinShowed = 0
+            }
             
-            let heightSign: CGFloat = CGFloat((30 / dataMessagesPin.count) - 1)
-            let widthSign: CGFloat = 2
+            DispatchQueue.main.async {
+                self.signSelectedPin.subviews.forEach({ $0.removeFromSuperview() })
+                self.signSelectedPin.removeFromSuperview()
+                self.containerPin.addSubview(self.signSelectedPin)
+                self.signSelectedPin.anchor(left: self.containerPin.leftAnchor, paddingLeft: 8, centerY: self.containerPin.centerYAnchor, width: 2, height: 30)
+                self.signSelectedPin.layer.cornerRadius = 1
+                self.signSelectedPin.clipsToBounds = true
+                self.signSelectedPin.alignment = .fill
+                self.signSelectedPin.axis = .vertical
+                self.signSelectedPin.distribution = .fill
+                self.signSelectedPin.spacing = dataMessagesPin.count == 3 ? 1.5 : 2
+                
+                let heightSign: CGFloat = CGFloat((30 / dataMessagesPin.count) - 1)
+                let widthSign: CGFloat = 2
 
-            for i in 0..<dataMessagesPin.count {
-                let viewSign = UIView()
-                viewSign.backgroundColor = (i == self.nextPinShowed) ? .white : .gray
-                viewSign.anchor(width: widthSign, height: heightSign)
-                viewSign.layer.cornerRadius = 1
-                viewSign.clipsToBounds = true
-                self.signSelectedPin.addArrangedSubview(viewSign)
-            }
-            self.animateLabelTextChange(label: self.textPin, newText: dataMessagesPin[self.nextPinShowed][TypeDataMessage.message_text] as? String ?? "")
+                for i in 0..<dataMessagesPin.count {
+                    let viewSign = UIView()
+                    viewSign.backgroundColor = (i == self.nextPinShowed) ? .white : .gray
+                    viewSign.anchor(width: widthSign, height: heightSign)
+                    viewSign.layer.cornerRadius = 1
+                    viewSign.clipsToBounds = true
+                    self.signSelectedPin.addArrangedSubview(viewSign)
+                }
+                self.animateLabelTextChange(label: self.textPin, newText: dataMessagesPin[self.nextPinShowed][TypeDataMessage.message_text] as? String ?? "")
+            }
         }
     }
     

+ 70 - 61
NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift

@@ -3324,42 +3324,49 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
             return
         }
         DispatchQueue.global().async {
-            while API.nGetCLXConnState() == 0 || !API.bInetConnAvailable() {
-                Thread.sleep(forTimeInterval: 1)
-            }
-            if let listGroupImages = self.groupImages.first(where: { $0.key == message_id }) {
-                let valueListGroupImages = listGroupImages.value
-                for i in 0..<valueListGroupImages.count {
-                    Database.shared.database?.inTransaction({ (fmdb, rollback) in
-                        do {
-                            _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
-                                "status" : "4"
-                            ], _where: "message_id = '\(valueListGroupImages[i].messageId)'")
-                        } catch {
-                            rollback.pointee = true
-                            print("Access database error: \(error.localizedDescription)")
-                        }
-                    })
-                    message.mStatus = CoreMessage_TMessageUtil.getTID()
-                    message.mBodies[CoreMessage_TMessageKey.L_PIN] = f_pin
-                    message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID] = "-2,\(valueListGroupImages[i].messageId)"
+            var isBackground = true
+            while isBackground {
+                DispatchQueue.main.sync {
+                    isBackground = API.nGetCLXConnState() == 0 || !API.bInetConnAvailable() || APIS.checkAppStateisBackground()
                 }
-            } else {
-                Database.shared.database?.inTransaction({ (fmdb, rollback) in
-                    do {
-                        _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
-                            "status" : "4"
-                        ], _where: "message_id = '\(message_id)'")
-                    } catch {
-                        rollback.pointee = true
-                        print("Access database error: \(error.localizedDescription)")
+                if isBackground {
+                    Thread.sleep(forTimeInterval: 1.0)
+                } else {
+                    if let listGroupImages = self.groupImages.first(where: { $0.key == message_id }) {
+                        let valueListGroupImages = listGroupImages.value
+                        for i in 0..<valueListGroupImages.count {
+                            Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                                do {
+                                    _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
+                                        "status" : "4"
+                                    ], _where: "message_id = '\(valueListGroupImages[i].messageId)'")
+                                } catch {
+                                    rollback.pointee = true
+                                    print("Access database error: \(error.localizedDescription)")
+                                }
+                            })
+                            message.mStatus = CoreMessage_TMessageUtil.getTID()
+                            message.mBodies[CoreMessage_TMessageKey.L_PIN] = f_pin
+                            message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID] = "-2,\(valueListGroupImages[i].messageId)"
+                        }
+                    } else {
+                        Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                            do {
+                                _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
+                                    "status" : "4"
+                                ], _where: "message_id = '\(message_id)'")
+                            } catch {
+                                rollback.pointee = true
+                                print("Access database error: \(error.localizedDescription)")
+                            }
+                        })
+                        message.mStatus = CoreMessage_TMessageUtil.getTID()
+                        message.mBodies[CoreMessage_TMessageKey.L_PIN] = f_pin
+                        message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID] = "-2,\(message_id)"
                     }
-                })
-                message.mStatus = CoreMessage_TMessageUtil.getTID()
-                message.mBodies[CoreMessage_TMessageKey.L_PIN] = f_pin
-                message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID] = "-2,\(message_id)"
+                    _ = Nexilis.write(message: message)
+                }
             }
-            _ = Nexilis.write(message: message)
         }
         if let index = dataMessages.firstIndex(where: {$0["message_id"] as? String == message_id}) {
             dataMessages[index]["status"] = "4"
@@ -8814,36 +8821,38 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
         obj.message_id = dataMessagesPin[nextPinShowed][TypeDataMessage.message_id] as? String ?? ""
         contentMessageTapped(obj)
         
-        if nextPinShowed < dataMessagesPin.count - 1 {
-            nextPinShowed+=1
-        } else {
-            nextPinShowed = 0
-        }
-        
-        DispatchQueue.main.async {
-            self.signSelectedPin.subviews.forEach({ $0.removeFromSuperview() })
-            self.signSelectedPin.removeFromSuperview()
-            self.containerPin.addSubview(self.signSelectedPin)
-            self.signSelectedPin.anchor(left: self.containerPin.leftAnchor, paddingLeft: 8, centerY: self.containerPin.centerYAnchor, width: 2, height: 30)
-            self.signSelectedPin.layer.cornerRadius = 1
-            self.signSelectedPin.clipsToBounds = true
-            self.signSelectedPin.alignment = .fill
-            self.signSelectedPin.axis = .vertical
-            self.signSelectedPin.distribution = .fill
-            self.signSelectedPin.spacing = dataMessagesPin.count == 3 ? 1.5 : 2
+        if dataMessagesPin.count > 0 {
+            if nextPinShowed < dataMessagesPin.count - 1 {
+                nextPinShowed+=1
+            } else {
+                nextPinShowed = 0
+            }
             
-            let heightSign: CGFloat = CGFloat((30 / dataMessagesPin.count) - 1)
-            let widthSign: CGFloat = 2
+            DispatchQueue.main.async {
+                self.signSelectedPin.subviews.forEach({ $0.removeFromSuperview() })
+                self.signSelectedPin.removeFromSuperview()
+                self.containerPin.addSubview(self.signSelectedPin)
+                self.signSelectedPin.anchor(left: self.containerPin.leftAnchor, paddingLeft: 8, centerY: self.containerPin.centerYAnchor, width: 2, height: 30)
+                self.signSelectedPin.layer.cornerRadius = 1
+                self.signSelectedPin.clipsToBounds = true
+                self.signSelectedPin.alignment = .fill
+                self.signSelectedPin.axis = .vertical
+                self.signSelectedPin.distribution = .fill
+                self.signSelectedPin.spacing = dataMessagesPin.count == 3 ? 1.5 : 2
+                
+                let heightSign: CGFloat = CGFloat((30 / dataMessagesPin.count) - 1)
+                let widthSign: CGFloat = 2
 
-            for i in 0..<dataMessagesPin.count {
-                let viewSign = UIView()
-                viewSign.backgroundColor = (i == self.nextPinShowed) ? .white : .gray
-                viewSign.anchor(width: widthSign, height: heightSign)
-                viewSign.layer.cornerRadius = 1
-                viewSign.clipsToBounds = true
-                self.signSelectedPin.addArrangedSubview(viewSign)
-            }
-            self.animateLabelTextChange(label: self.textPin, newText: dataMessagesPin[self.nextPinShowed][TypeDataMessage.message_text] as? String ?? "")
+                for i in 0..<dataMessagesPin.count {
+                    let viewSign = UIView()
+                    viewSign.backgroundColor = (i == self.nextPinShowed) ? .white : .gray
+                    viewSign.anchor(width: widthSign, height: heightSign)
+                    viewSign.layer.cornerRadius = 1
+                    viewSign.clipsToBounds = true
+                    self.signSelectedPin.addArrangedSubview(viewSign)
+                }
+                self.animateLabelTextChange(label: self.textPin, newText: dataMessagesPin[self.nextPinShowed][TypeDataMessage.message_text] as? String ?? "")
+            }
         }
     }
     

+ 390 - 13
NexilisLite/NexilisLite/Source/View/Chat/PreviewAttachmentImageVideo.swift

@@ -44,6 +44,13 @@ class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITex
     var tableViewConfigFile: UITableView!
     var specFileString = ""
     
+    var lastPositionCursorMention = 0
+    var lastTextLength = 0
+    var tableMention = UITableView()
+    var heightTableMention: NSLayoutConstraint!
+    var listMentionWithText:[User] = []
+    var listMentionInTextField:[User] = []
+    
     override func viewDidLoad() {
         super.viewDidLoad()
         
@@ -140,7 +147,7 @@ class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITex
             
             textFieldSend.layer.cornerRadius = textFieldSend.maxCornerRadius()
             textFieldSend.layer.borderWidth = 1.0
-            textFieldSend.backgroundColor = .white.withAlphaComponent(0.5)
+            textFieldSend.backgroundColor = .white
             if (currentTextTextField == "" || currentTextTextField == nil) {
                 textFieldSend.text = "Send message".localized()
                 textFieldSend.textColor = UIColor.lightGray
@@ -159,7 +166,8 @@ class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITex
             center.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
             
             let dismissKeyboard = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
-            view.addGestureRecognizer(dismissKeyboard)
+            dismissKeyboard.cancelsTouchesInView = false
+            scrollViewImage.addGestureRecognizer(dismissKeyboard)
         }
         
         buttonCancel.circle()
@@ -169,6 +177,22 @@ class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITex
         buttonSpecFile.circle()
         buttonSpecFile.backgroundColor = .secondaryColor.withAlphaComponent(0.4)
         buttonSpecFile.addTarget(self, action: #selector(showSpecFile), for: .touchUpInside)
+        
+        if let vc = delegate as? EditorGroup {
+            self.listMentionWithText = vc.listMentionWithText
+            self.listMentionInTextField = vc.listMentionInTextField
+            tableMention = UITableView()
+            tableMention.register(UITableViewCell.self, forCellReuseIdentifier: "cellMention")
+            tableMention.dataSource = self
+            tableMention.delegate = self
+            tableMention.contentInset = UIEdgeInsets(top: -25, left: 0, bottom: 0, right: 0)
+            tableMention.backgroundColor = .white
+            self.view.addSubview(tableMention)
+            tableMention.anchor(left: view.leftAnchor, bottom: textFieldSend.topAnchor, right: view.rightAnchor)
+            heightTableMention = tableMention.heightAnchor.constraint(equalToConstant: 0)
+            self.heightTableMention.isActive = true
+        }
+        lastTextLength = textFieldSend.text.count
     }
     
     @objc func showChooserACKConfidential() {
@@ -209,19 +233,73 @@ class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITex
     }
     
     func setPreviousVariableMessageMode() {
-        let stack = self.presentingViewController as! UINavigationController
-        let vc = stack.viewControllers[stack.viewControllers.count - 1]
+        let vc = delegate
         if vc is EditorPersonal {
             let editorVc = vc as! EditorPersonal
             editorVc.setAckConfidential(isAck: self.isAck, isConfidential: self.isConfidential)
-        } else {
+        } else if vc is EditorGroup {
             let editorVc = vc as! EditorGroup
             editorVc.setAckConfidential(isAck: self.isAck, isConfidential: self.isConfidential)
         }
     }
     
     func textViewDidChange(_ textView: UITextView) {
-        textView.attributedText = textView.text.richText(isEditing: true)
+        //indention code:
+        let text = textView.text ?? ""
+        let cursorPosition = textView.selectedRange.location
+        
+        let tempListMention = listMentionInTextField
+        if listMentionInTextField.count > 0 {
+            for j in 0..<listMentionInTextField.count {
+                var index = j
+                if tempListMention.count != listMentionInTextField.count {
+                    index = j - (tempListMention.count - listMentionInTextField.count)
+                }
+                var upper = (Int(listMentionInTextField[index].ex_block ?? "0") ?? 0)
+                if cursorPosition <= upper {
+                    upper += text.count - lastTextLength
+                    listMentionInTextField[index].ex_block = "\(upper)"
+                }
+                let lower = upper - listMentionInTextField[index].fullName.count
+                let name = listMentionInTextField[index].fullName.trimmingCharacters(in: .whitespaces)
+                if textView.text.substring(from: lower, to: upper) != "@\(name)" {
+                    listMentionInTextField.remove(at: index)
+                }
+            }
+        }
+        
+        // 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)
+            }
+        }
+
+        // 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)
+            }
+        }
+
+        handleRichText(textView)
+        lastTextLength = text.count
     }
     
     func textViewDidBeginEditing(_ textView: UITextView) {
@@ -239,18 +317,256 @@ class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITex
     }
     
     func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
-        let cursorPosition = textView.caretRect(for: self.textFieldSend.selectedTextRange!.start).origin
-        let currentLine = Int(cursorPosition.y / self.textFieldSend.font!.lineHeight)
-        UIView.animate(withDuration: 0.3) {
-            if currentLine == 0 {
-                self.heightTextFieldSend.constant = 40
-            } else if currentLine < 4 {
-                self.heightTextFieldSend.constant = self.textFieldSend.contentSize.height// Padding
+        if text.isEmpty {
+            if let vc = delegate as? EditorGroup {
+                if listMentionInTextField.count > 0 {
+                    for i in 0..<listMentionInTextField.count {
+                        if lastPositionCursorMention == Int(listMentionInTextField[i].ex_block!)! + 1 {
+                            let fulltextForMention = textView.text.substring(from: 0, to: lastPositionCursorMention - 1)
+                            let diff = textView.text.count - fulltextForMention.count
+                            var text = textView.text ?? ""
+                            let nameMention = listMentionInTextField[i].fullName.trimmingCharacters(in: .whitespaces)
+                            let rangeReplacement = NSRange(location: lastPositionCursorMention - nameMention.count - 1, length: nameMention.count + 1)
+                            let replacementText = ""
+                            
+                            let copyAttributedText = text.richText(isEditing: true, group_id: vc.dataGroup["group_id"]  as? String ?? "", listMentionInTextField: listMentionInTextField)
+                            copyAttributedText.removeAttribute(.foregroundColor, range: rangeReplacement)
+                            
+                            textView.attributedText = copyAttributedText
+
+                            // Replace the old text with the new text using the replaceSubrange(_:with:) method
+                            if let startIndex = text.index(text.startIndex, offsetBy: rangeReplacement.location, limitedBy: text.endIndex),
+                               let endIndex = text.index(startIndex, offsetBy: rangeReplacement.length, limitedBy: text.endIndex) {
+                                text.replaceSubrange(startIndex..<endIndex, with: replacementText)
+                            }
+                            listMentionInTextField.remove(at: i)
+                            
+                            textView.attributedText = text.richText(isEditing: true, group_id: vc.dataGroup["group_id"]  as? String ?? "", listMentionInTextField: listMentionInTextField)
+                            
+                            let newPosition = textView.position(from: textView.beginningOfDocument, offset: textView.text.count - diff)
+                            textView.selectedTextRange = textView.textRange(from: newPosition!, to: newPosition!)
+                            textViewDidChangeSelection(textView)
+                            handleRichText(textView)
+                            return false
+                        }
+                    }
+                }
+            }
+        }
+        let indent = handleIndent(textView, range, text)
+        if !indent {
+            textViewDidChangeSelection(textView)
+            handleRichText(textView)
+            return indent
+        }
+        if (textView.text.count == 0) {
+            return text != "\n"
+        }
+        return true
+    }
+    
+    private func handleIndent(_ textView: UITextView, _ range: NSRange, _ text: String) -> Bool {
+        guard let nsText = textView.text as NSString? else { return true }
+        let newText = nsText.replacingCharacters(in: range, with: text)
+        var lines = newText.components(separatedBy: "\n")
+        
+        // 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 {
+            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.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.utf16.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
+        }
+        
+        // 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
+        }
         return true
     }
     
+    private func handleRichText(_ textView: UITextView) {
+        if let vc = delegate as? EditorGroup {
+            textView.preserveCursorPosition(withChanges: { _ in
+                textView.attributedText = textView.text.richText(isEditing: true, group_id: vc.dataGroup["group_id"]  as? String ?? "", listMentionInTextField: self.listMentionInTextField)
+                return .preserveCursor
+            })
+        } else {
+            textView.preserveCursorPosition(withChanges: { _ in
+                textView.attributedText = textView.text.richText(isEditing: true)
+                return .preserveCursor
+            })
+        }
+    }
+    
+    func textViewDidChangeSelection(_ textView: UITextView) {
+        if let vc = delegate as? EditorGroup {
+            lastPositionCursorMention = textView.selectedRange.location
+            var isShowMention = false
+
+            let fulltextForMention = textView.text.prefix(lastPositionCursorMention)
+            
+            let lines = fulltextForMention.split(separator: "\n")
+            if let lastLineIndex = lines.lastIndex(where: { !$0.isEmpty }) {
+                let words = lines[lastLineIndex].split(separator: " ")
+                if let lastWordIndex = words.lastIndex(where: { !$0.isEmpty }) {
+                    let mentionText = words[lastWordIndex]
+                    let lastChar = fulltextForMention.last
+                    if lastChar != "\n" && lastChar != " " {
+                        if mentionText.starts(with: "@") || (mentionText.count >= 2 && (self.textFieldSend.textColor != UIColor.lightGray) && extractFromAtIfSymbolsBefore(String(mentionText)) == nil) {
+                            showMention(text: mentionText.starts(with: "@") ? String(mentionText.dropFirst()) : String(mentionText))
+                            isShowMention = true
+                        } else if let textM = extractFromAtIfSymbolsBefore(String(mentionText)) {
+                            showMention(text: String(textM.dropFirst()))
+                            isShowMention = true
+                        }
+                    }
+                }
+            }
+            
+            if !isShowMention {
+                hideMention()
+            }
+        }
+        let nowTextFieldSend = self.textFieldSend
+        let cursorPosition = textView.caretRect(for: nowTextFieldSend!.selectedTextRange!.start).origin
+        let doubleCurrentLine = cursorPosition.y / nowTextFieldSend!.font!.lineHeight
+        if doubleCurrentLine.isFinite {
+            let currentLine = Int(ceil(doubleCurrentLine))
+            UIView.animate(withDuration: 0.3) {
+                let layoutManager = textView.layoutManager
+                var numberOfLines = 0
+                var index = 0
+                let numberOfGlyphs = layoutManager.numberOfGlyphs
+
+                while index < numberOfGlyphs {
+                    var lineRange = NSRange()
+                    layoutManager.lineFragmentRect(forGlyphAt: index, effectiveRange: &lineRange)
+                    index = NSMaxRange(lineRange)
+                    numberOfLines += 1
+                }
+                if currentLine == 1 && (numberOfLines == 1 || numberOfLines == 0) {
+                    self.heightTextFieldSend.constant = 40
+                } else if (self.heightTextFieldSend.constant < 95.0 || (self.constraintViewTextField != nil && self.constraintViewTextField.constant < 95.0)) && currentLine >= 4 {
+                    self.heightTextFieldSend.constant = 95.0
+                } else if currentLine < 4 && numberOfLines < 5 {
+                    if (nowTextFieldSend!.text.count > 0 && self.heightTextFieldSend.constant != nowTextFieldSend!.contentSize.height) {
+                        self.heightTextFieldSend.constant = nowTextFieldSend!.contentSize.height
+                    }
+                }
+            }
+        }
+    }
+    
+    func extractFromAtIfSymbolsBefore(_ text: String) -> String? {
+        guard let atIndex = text.firstIndex(of: "@") else {
+            return nil
+        }
+        
+        let beforeAt = text[..<atIndex]
+        let afterAt = text[atIndex...]
+
+        // Define symbols as anything that's not a letter or digit
+        let symbolSet = CharacterSet.letters.union(.decimalDigits).inverted
+        let isAllSymbols = beforeAt.unicodeScalars.allSatisfy { symbolSet.contains($0) }
+
+        return isAllSymbols ? String(afterAt) : nil
+    }
+    
+    private func showMention(text: String) {
+        listMentionWithText.removeAll()
+        Database.shared.database?.inTransaction({ fmdb, rollback in
+            let vc = delegate as! EditorGroup
+            do {
+                let idMe = User.getMyPin()!
+                if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "SELECT f_pin, first_name || ' ' || ifnull(last_name, '') name FROM GROUPZ_MEMBER where group_id='\(vc.dataGroup["group_id"]  as? String ?? "")' AND f_pin <> '\(idMe)' AND name LIKE '%\(text)%'") {
+                    while cursor.next() {
+                        let user = User(pin: "")
+                        user.pin = cursor.string(forColumnIndex: 0) ?? ""
+                        user.firstName = cursor.string(forColumnIndex: 1) ?? ""
+                        if !user.pin.isEmpty {
+                            let userFromBuddy = User.getDataCanNil(pin: user.pin, fmdb: fmdb)
+                            if userFromBuddy != nil {
+                                listMentionWithText.append(userFromBuddy!)
+                            } else {
+                                listMentionWithText.append(user)
+                            }
+                        }
+                    }
+                    cursor.close()
+                }
+                listMentionWithText.removeAll(where: { listMentionInTextField.contains($0) })
+                var nowTableMention = tableMention
+                var nowHeightTableMention = heightTableMention!
+                if listMentionWithText.count > 0 {
+                    if listMentionWithText.count < 5 {
+                        nowHeightTableMention.constant = CGFloat(44 * listMentionWithText.count)
+                    } else {
+                        nowHeightTableMention.constant = 44 * 4
+                    }
+                    nowTableMention.reloadData()
+                } else {
+                    nowHeightTableMention.constant = 44
+                    self.hideMention()
+                }
+            } catch {
+                rollback.pointee = true
+                print("Access database error: \(error.localizedDescription)")
+            }
+        })
+    }
+    
+    private func hideMention() {
+        if self.heightTableMention != nil && self.heightTableMention.constant != 0 {
+            listMentionWithText.removeAll()
+            tableMention.reloadData()
+            self.heightTableMention.constant = 0
+        }
+    }
+    
     @objc func previewImageVideoTapped(_ sender: ObjectGesture) {
         let player = AVPlayer(url: sender.videoURL! as URL)
         let playerVC = AVPlayerViewController()
@@ -619,6 +935,47 @@ extension PreviewAttachmentImageVideo: UITableViewDelegate, UITableViewDataSourc
     
     func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
         tableView.deselectRow(at: indexPath, animated: true)
+        if tableView == tableMention, let vc = delegate as? EditorGroup {
+            tableView.deselectRow(at: indexPath, animated: true)
+            let nowTextField = textFieldSend!
+            let fulltextForMention = nowTextField.text.substring(from: 0, to: lastPositionCursorMention - 1)
+            let diff = nowTextField.text.count - fulltextForMention.count
+            let lines = fulltextForMention.split(separator: "\n")
+            if let lastLineIndex = lines.lastIndex(where: { !$0.isEmpty }) {
+                let words = lines[lastLineIndex].split(separator: " ")
+                if let lastWordIndex = words.lastIndex(where: { !$0.isEmpty }) {
+                    var lastWord = words[lastWordIndex]
+                    if let textM = extractFromAtIfSymbolsBefore(String(lastWord)) {
+                        lastWord = textM[textM.startIndex..<textM.endIndex]
+                    }
+                    if let rangeLastWord = fulltextForMention.range(of: lastWord, options: .backwards) {
+                        listMentionInTextField.append(listMentionWithText[indexPath.row])
+                        
+                        var addSpaceAfterReplacement = ""
+                        if diff == 0 {
+                            addSpaceAfterReplacement = " "
+                        }
+                        
+                        var text = nowTextField.text ?? ""
+                        let nameMention = listMentionWithText[indexPath.row].fullName.trimmingCharacters(in: .whitespaces)
+                        listMentionInTextField.last?.ex_block = "\(fulltextForMention.distance(from: fulltextForMention.startIndex, to: rangeLastWord.lowerBound) + nameMention.count)" //upperbound
+                        let replacementText = "@\(nameMention)"
+                        
+                        // Replace the old text with the new text using the replaceSubrange(_:with:) method
+                        text.replaceSubrange(rangeLastWord, with: replacementText + addSpaceAfterReplacement)
+                        
+                        nowTextField.attributedText = text.richText(isEditing: true, group_id: vc.dataGroup["group_id"]  as? String ?? "", listMentionInTextField: listMentionInTextField)
+                        
+                        let newPosition = nowTextField.position(from: nowTextField.beginningOfDocument, offset: nowTextField.text.count - diff)
+                        nowTextField.selectedTextRange = nowTextField.textRange(from: newPosition!, to: newPosition!)
+                        
+                        hideMention()
+                        lastTextLength = nowTextField.text.count
+                        return
+                    }
+                }
+            }
+        }
         var type = ""
         if indexPath.row == 0 {
             type = "share,download"
@@ -645,6 +1002,21 @@ extension PreviewAttachmentImageVideo: UITableViewDelegate, UITableViewDataSourc
     }
     
     func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        if tableView == tableMention {
+            let cellMention = tableView.dequeueReusableCell(withIdentifier: tableView == tableMention ? "cellMention" : "cellEditMention", for: indexPath as IndexPath)
+            var content = cellMention.defaultContentConfiguration()
+            content.textProperties.font = UIFont.systemFont(ofSize: 11 + offset())
+            content.imageProperties.tintColor = .black
+            content.imageProperties.maximumSize = CGSize(width: 24, height: 24)
+            if indexPath.row < listMentionWithText.count {
+                getImage(name: listMentionWithText[indexPath.row].thumb, placeholderImage: UIImage(systemName: "person"), isCircle: true, tableView: tableView, indexPath: indexPath, completion: { result, isDownloaded, image in
+                    content.image = image
+                })
+                content.text = listMentionWithText[indexPath.row].firstName + " " + listMentionWithText[indexPath.row].lastName
+            }
+            cellMention.contentConfiguration = content
+            return cellMention
+        }
         let cell = tableView.dequeueReusableCell(withIdentifier: "cellConfigFile", for: indexPath as IndexPath)
         var content = cell.defaultContentConfiguration()
         content.textProperties.font = .systemFont(ofSize: 16, weight: .medium)
@@ -664,4 +1036,9 @@ extension PreviewAttachmentImageVideo: UITableViewDelegate, UITableViewDataSourc
         cell.tintColor = .black
         return cell
     }
+    
+    func offset() -> CGFloat{
+        guard let fontSize = Int(SecureUserDefaults.shared.value(forKey: "font_size") ?? "0") else { return 0 }
+        return CGFloat(fontSize)
+    }
 }

+ 2 - 0
NexilisLite/NexilisLite/Source/View/Control/BackupRestoreView.swift

@@ -46,6 +46,7 @@ public class BackupRestoreView: UIViewController, UITableViewDataSource, UITable
         navigationController?.navigationBar.topItem?.backButtonTitle = "Back".localized()
         
         tableView = UITableView()
+        tableView.backgroundColor = .clear
         view.addSubview(tableView)
         tableView.anchor(top: view.safeAreaLayoutGuide.topAnchor, left: view.leftAnchor, bottom: view.safeAreaLayoutGuide.bottomAnchor, right: view.rightAnchor)
         tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cellBackupRestore")
@@ -338,6 +339,7 @@ public class BackupRestoreView: UIViewController, UITableViewDataSource, UITable
     
     private func makeViewBackup(cell: UITableViewCell, indexPath: IndexPath) {
         cell.contentView.subviews.forEach { $0.removeFromSuperview() }
+        cell.backgroundColor = .clear
         if indexPath.section == 0 {
             cell.selectionStyle = .none
             let container = UIView()