Переглянути джерело

update for release 5.0.40

alqindiirsyam 2 місяців тому
батько
коміт
466d7d2e28

+ 97 - 18
NexilisLite/NexilisLite/Source/APIS.swift

@@ -86,6 +86,16 @@ public class APIS: NSObject {
             APIS.showChangeProfile()
             return
         }
+        if !Nexilis.checkingAccess(key: "call_center") {
+            if Nexilis.checkingAccessAlert(key: "call_center") != "|" && !Nexilis.checkingAccessAlert(key: "call_center").isEmpty {
+                let title = Nexilis.checkingAccessAlert(key: "call_center").components(separatedBy: "|")[0]
+                let message = Nexilis.checkingAccessAlert(key: "call_center").components(separatedBy: "|")[1]
+                APIS.nexilisShowAlertWithHTMLMessage(on: UIApplication.shared.visibleViewController ?? UIViewController(), title: title, message: message)
+            } else {
+                UIApplication.shared.visibleViewController?.view.makeToast("Feature disabled".localized(), duration: 5)
+            }
+            return
+        }
         if User.isCallCenter(userType: (User.getData(pin: User.getMyPin())?.userType)!) {
             let controller = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "myHistoryCC") as! HistoryCCViewController
             controller.isOfficer = true
@@ -168,6 +178,10 @@ public class APIS: NSObject {
             APIS.showChangeProfile()
             return
         }
+        if !Nexilis.checkingAccess(key: "notification_center") {
+            UIApplication.shared.visibleViewController?.view.makeToast("Feature disabled".localized(), duration: 5)
+            return
+        }
         let controller = HistoryBroadcastViewController()
         let navigationController = CustomNavigationController(rootViewController: controller)
         navigationController.defaultStyle()
@@ -267,6 +281,16 @@ public class APIS: NSObject {
             APIS.showChangeProfile()
             return
         }
+        if !Nexilis.checkingAccess(key: "live_streaming") {
+            if Nexilis.checkingAccessAlert(key: "live_streaming") != "|" && !Nexilis.checkingAccessAlert(key: "live_streaming").isEmpty {
+                let title = Nexilis.checkingAccessAlert(key: "live_streaming").components(separatedBy: "|")[0]
+                let message = Nexilis.checkingAccessAlert(key: "live_streaming").components(separatedBy: "|")[1]
+                APIS.nexilisShowAlertWithHTMLMessage(on: UIApplication.shared.visibleViewController ?? UIViewController(), title: title, message: message)
+            } else {
+                UIApplication.shared.visibleViewController?.view.makeToast("Feature disabled".localized(), duration: 5)
+            }
+            return
+        }
         let navigationController = CustomNavigationController(rootViewController: QmeraCreateStreamingViewController())
         navigationController.defaultStyle()
         if UIApplication.shared.visibleViewController?.navigationController != nil {
@@ -282,6 +306,16 @@ public class APIS: NSObject {
             APIS.showChangeProfile()
             return
         }
+        if !Nexilis.checkingAccess(key: "vconf_room") {
+            if Nexilis.checkingAccessAlert(key: "vconf_room") != "|" && !Nexilis.checkingAccessAlert(key: "vconf_room").isEmpty {
+                let title = Nexilis.checkingAccessAlert(key: "vconf_room").components(separatedBy: "|")[0]
+                let message = Nexilis.checkingAccessAlert(key: "vconf_room").components(separatedBy: "|")[1]
+                APIS.nexilisShowAlertWithHTMLMessage(on: UIApplication.shared.visibleViewController ?? UIViewController(), title: title, message: message)
+            } else {
+                UIApplication.shared.visibleViewController?.view.makeToast("Feature disabled".localized(), duration: 5)
+            }
+            return
+        }
         let navigationController = CustomNavigationController(rootViewController: CreateSeminarViewController())
         navigationController.defaultStyle()
         if UIApplication.shared.visibleViewController?.navigationController != nil {
@@ -297,6 +331,10 @@ public class APIS: NSObject {
             APIS.showChangeProfile()
             return
         }
+        if !Nexilis.checkingAccess(key: "audio_call") {
+            UIApplication.shared.visibleViewController?.view.makeToast("Feature disabled".localized(), duration: 5)
+            return
+        }
         let callContact = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "contactSID") as! ContactCallViewController
         callContact.onlyAudioOrVideo = 1
         let navigationController = CustomNavigationController(rootViewController: callContact)
@@ -313,6 +351,10 @@ public class APIS: NSObject {
             UIApplication.shared.visibleViewController?.view.makeToast("92:Username is empty".localized(), duration: 3)
             return
         }
+        if !Nexilis.checkingAccess(key: "audio_call") {
+            UIApplication.shared.visibleViewController?.view.makeToast("Feature disabled".localized(), duration: 5)
+            return
+        }
         let user = User.getDataFromNameCanNil(name: name)
         if user == nil {
             UIApplication.shared.visibleViewController?.view.makeToast("91:Invalid name or you must add Username to your contact first".localized(), duration: 3)
@@ -342,6 +384,10 @@ public class APIS: NSObject {
             APIS.showChangeProfile()
             return
         }
+        if !Nexilis.checkingAccess(key: "video_call") {
+            UIApplication.shared.visibleViewController?.view.makeToast("Feature disabled".localized(), duration: 5)
+            return
+        }
         let callContact = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "contactSID") as! ContactCallViewController
         callContact.onlyAudioOrVideo = 2
         let navigationController = CustomNavigationController(rootViewController: callContact)
@@ -358,6 +404,10 @@ public class APIS: NSObject {
             UIApplication.shared.visibleViewController?.view.makeToast("92:Username is empty".localized(), duration: 3)
             return
         }
+        if !Nexilis.checkingAccess(key: "video_call") {
+            UIApplication.shared.visibleViewController?.view.makeToast("Feature disabled".localized(), duration: 5)
+            return
+        }
         let user = User.getDataFromNameCanNil(name: name)
         if user == nil {
             UIApplication.shared.visibleViewController?.view.makeToast("91:Invalid name or you must add Username to your contact first".localized(), duration: 3)
@@ -395,6 +445,10 @@ public class APIS: NSObject {
             APIS.showChangeProfile()
             return
         }
+        if !Nexilis.checkingAccess(key: "broadcast_message") {
+            UIApplication.shared.visibleViewController?.view.makeToast("Feature disabled".localized(), duration: 5)
+            return
+        }
         let controller = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "broadcastNav")
         if UIApplication.shared.visibleViewController?.navigationController != nil {
             UIApplication.shared.visibleViewController?.navigationController?.present(controller, animated: true, completion: nil)
@@ -494,14 +548,13 @@ public class APIS: NSObject {
             closeButton.backgroundColor = .lightGray.withAlphaComponent(0.1)
             let config = UIImage.SymbolConfiguration(pointSize: 18, weight: .semibold)
             closeButton.setImage(UIImage(systemName: "xmark", withConfiguration: config), for: .normal)
-            closeButton.actionHandle(controlEvents: .touchUpInside,
-                                     ForAction:{() -> Void in
+            closeButton.addAction(UIAction { _ in
                 startedNewCommunity.dismiss(animated: true)
-            })
+            }, for: .touchUpInside)
             
             let imageComm = UIImageView(image: UIImage(named: "pb_community_social", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!)
             containerView.addSubview(imageComm)
-            imageComm.anchor(top: closeButton.bottomAnchor, paddingTop: -40, centerX: containerView.centerXAnchor, width: 380, height: 250)
+            imageComm.anchor(top: closeButton.bottomAnchor, paddingTop: -40, centerX: containerView.centerXAnchor, width: 350, height: 250)
             
             let titleComm = UILabel()
             containerView.addSubview(titleComm)
@@ -530,6 +583,12 @@ public class APIS: NSObject {
             buttonComm.setTitle("Get started".localized(), for: .normal)
             buttonComm.setTitleColor(.white, for: .normal)
             buttonComm.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
+            buttonComm.addAction(UIAction { _ in
+                startedNewCommunity.dismiss(animated: true) {
+                    let navigationController = UINavigationController(rootViewController: CommunityNew())
+                    UIApplication.shared.visibleViewController?.present(navigationController, animated: true, completion: nil)
+                }
+            }, for: .touchUpInside)
         }
         startedNewCommunity.modalPresentationStyle = .overCurrentContext
         if UIApplication.shared.visibleViewController?.navigationController != nil {
@@ -1608,6 +1667,7 @@ public class APIS: NSObject {
             FileEncryption.shared.aesKey = nil
             FileEncryption.shared.aesIV = nil
         }
+        FloatingButton.datePull = nil
     }
     
     public static var notifTimer = Timer()
@@ -1639,20 +1699,20 @@ public class APIS: NSObject {
         checkDataForShareExtension()
         UIApplication.shared.applicationIconBadgeNumber = 0
         UNUserNotificationCenter.current().removeAllDeliveredNotifications()
-        DispatchQueue.global().async {
-            while API.nGetCLXConnState() == 0 {
-                Thread.sleep(forTimeInterval: 0.5)
-            }
-            if let vers = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.checkVersion()) {
-                let dataVersion = vers.getBody(key: CoreMessage_TMessageKey.DATA)
-                let type = vers.getBody(key: CoreMessage_TMessageKey.TYPE)
-                if dataVersion != "1" {
-                    DispatchQueue.main.async {
-                        showExpiredVersion(mandatory: type == "1")
-                    }
-                }
-            }
-        }
+//        DispatchQueue.global().async {
+//            while API.nGetCLXConnState() == 0 {
+//                Thread.sleep(forTimeInterval: 0.5)
+//            }
+//            if let vers = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.checkVersion()) {
+//                let dataVersion = vers.getBody(key: CoreMessage_TMessageKey.DATA)
+//                let type = vers.getBody(key: CoreMessage_TMessageKey.TYPE)
+//                if dataVersion != "1" {
+//                    DispatchQueue.main.async {
+//                        showExpiredVersion(mandatory: type == "1")
+//                    }
+//                }
+//            }
+//        }
         if Utils.getSecureFolderOffline() == "0" && afterEnterBackground && Database.shared.database == nil && Utils.getSetProfile() {
             Database.recreateInstance()
             NotificationCenter.default.post(name: NSNotification.Name(rawValue: "disconnected_nexilis"), object: nil, userInfo: nil)
@@ -1692,6 +1752,25 @@ public class APIS: NSObject {
         }
     }
     
+    public static func nexilisShowAlertWithHTMLMessage(on viewController: UIViewController, title: String, message: String = "<b>Bold</b> and <i>italic</i> text in an alert") {
+        let alert = UIAlertController(title: title, message: "", preferredStyle: .alert)
+        
+        let titleFont = UIFont.boldSystemFont(ofSize: 16)
+        let titleAttributes = [NSAttributedString.Key.font: titleFont]
+        alert.setValue(NSAttributedString(string: title, attributes: titleAttributes), forKey: "attributedTitle")
+        
+        var message = message
+        message = message.replacingOccurrences(of: "<b>", with: "*")
+        message = message.replacingOccurrences(of: "</b>", with: "*")
+        message = message.replacingOccurrences(of: "<i>", with: "_")
+        message = message.replacingOccurrences(of: "</i>", with: "_")
+        
+        alert.setValue(message.richText(fontSize: 14), forKey: "attributedMessage")
+        
+        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
+        viewController.present(alert, animated: true, completion: nil)
+    }
+    
     private static func showEnableNotificationsAlert() {
         guard !isAlertPresented else { return }
         isAlertPresented = true

+ 3 - 1
NexilisLite/NexilisLite/Source/Database.swift

@@ -91,7 +91,9 @@ public class Database {
 
     
     func openDatabase() -> Int {
-        setDBInstance()
+        if FileEncryption.shared.aesKey != nil {
+            setDBInstance()
+        }
         var result = 0
         database?.inTransaction({(fmdb, rollback) in
             do {

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

@@ -615,6 +615,18 @@ extension UIColor {
         return renderColor(hex: "#E5FCE4")
     }
     
+    public static var whatsappGrayPPColor: UIColor {
+        return renderColor(hex: "#a1a5b1")
+    }
+    
+    public static var waGrayLight: UIColor {
+        return renderColor(hex: "#eceaeb")
+    }
+    
+    public static var waGrayFont: UIColor {
+        return renderColor(hex: "#b7b5b6")
+    }
+    
     public class func renderColor(hex: String) -> UIColor {
         var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
 
@@ -649,6 +661,18 @@ extension UIView {
         return (self.frame.width > self.frame.height) ? self.frame.height / 2 : self.frame.width / 2
     }
     
+    public func currentFirstResponder() -> UIView? {
+        if self.isFirstResponder {
+            return self
+        }
+        for subview in self.subviews {
+            if let responder = subview.currentFirstResponder() {
+                return responder
+            }
+        }
+        return nil
+    }
+    
 }
 
 extension String {
@@ -784,6 +808,7 @@ extension String {
     }
     
     public func richText(
+        fontSize: CGFloat = 12 + String.offset(),
         isEditing: Bool = false,
         isSearching: Bool = false,
         textSearch: String = "",
@@ -791,10 +816,10 @@ extension String {
         listMentionInTextField: [User] = []
     ) -> NSMutableAttributedString {
         
-        let font = UIFont.systemFont(ofSize: 12 + String.offset())
-        let boldFont = UIFont.boldSystemFont(ofSize: 12 + String.offset())
-        let italicFont = UIFont.italicSystemFont(ofSize: 12 + String.offset())
-        let boldItalicFont = UIFont.systemFont(ofSize: 12 + String.offset(), weight: .semibold)
+        let font = UIFont.systemFont(ofSize: fontSize)
+        let boldFont = UIFont.boldSystemFont(ofSize: fontSize)
+        let italicFont = UIFont.italicSystemFont(ofSize: fontSize)
+        let boldItalicFont = UIFont.systemFont(ofSize: fontSize, weight: .semibold)
         
         let textUTF8 = self
         let finalText = NSMutableAttributedString(string: textUTF8, attributes: [.font: font])

+ 18 - 23
NexilisLite/NexilisLite/Source/FileEncryption.swift

@@ -34,34 +34,29 @@ public class FileEncryption {
     }
     
     public func writeSecure(filename: String? = nil, data: Data? = nil) throws {
-        DispatchQueue.global().async {
-            do {
-                while Utils.getFeatureAccess().isEmpty {
-                    Thread.sleep(forTimeInterval: 1)
-                }
-                let fileManager = FileManager.default
+        do {
+            let fileManager = FileManager.default
 
-                // Get the app's Documents directory
-                let documentDir = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
+            // Get the app's Documents directory
+            let documentDir = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
 
-                // Optional: Create a "Secure" subdirectory
-                let secureDir = documentDir.appendingPathComponent("Secure", isDirectory: true)
-                if !fileManager.fileExists(atPath: secureDir.path) {
-                    try fileManager.createDirectory(at: secureDir, withIntermediateDirectories: true)
-                }
+            // Optional: Create a "Secure" subdirectory
+            let secureDir = documentDir.appendingPathComponent("Secure", isDirectory: true)
+            if !fileManager.fileExists(atPath: secureDir.path) {
+                try fileManager.createDirectory(at: secureDir, withIntermediateDirectories: true)
+            }
 
-                // Create the file URL
-                let fileURL = secureDir.appendingPathComponent(filename ?? "")
+            // Create the file URL
+            let fileURL = secureDir.appendingPathComponent(filename ?? "")
 
-                // Encrypt the data
-                let sealedBox = try AES.GCM.seal(data ?? Data(), using: MasterKeyUtil.shared.getMasterKey())
-                let encryptedData = sealedBox.combined!
+            // Encrypt the data
+            let sealedBox = try AES.GCM.seal(data ?? Data(), using: MasterKeyUtil.shared.getMasterKey())
+            let encryptedData = sealedBox.combined!
 
-                // Write encrypted data to file
-                try encryptedData.write(to: fileURL)
-            } catch {
-                
-            }
+            // Write encrypted data to file
+            try encryptedData.write(to: fileURL)
+        } catch {
+            
         }
     }
     

+ 7 - 7
NexilisLite/NexilisLite/Source/FloatingButton/FloatingButton.swift

@@ -33,7 +33,7 @@ public class FloatingButton: UIView, UIGestureRecognizerDelegate {
     let labelCounterFB = UILabel()
     let indicatorCounterFBBig = UIImageView()
     
-    var datePull: Date?
+    static var datePull: Date?
     var animationTimer = Timer()
     var configAnim: Int = Int(Utils.getFloatingAnim().components(separatedBy: "~")[0]) ?? 1
     var isLoopingAnim = (Int(Utils.getFloatingAnim().components(separatedBy: "~")[1]) ?? 1) == 1 ? true : false
@@ -331,9 +331,9 @@ public class FloatingButton: UIView, UIGestureRecognizerDelegate {
     }
     
     private func pullButton() {
-        if datePull == nil || Int(Date().timeIntervalSince(datePull!)) >= 60 {
-            datePull = Date()
-        } else if Int(Date().timeIntervalSince(datePull!)) < 60 {
+        if FloatingButton.datePull == nil || Int(Date().timeIntervalSince(FloatingButton.datePull!)) >= 60 {
+            FloatingButton.datePull = Date()
+        } else if Int(Date().timeIntervalSince(FloatingButton.datePull!)) < 60 {
             return
         }
         if groupView.subviews.count == 0 {
@@ -386,10 +386,10 @@ public class FloatingButton: UIView, UIGestureRecognizerDelegate {
                 if !Utils.getHistoryPullFB().isEmpty {
                     setFBFromPull()
                 }
-                while Nexilis.isProcessWriteSync {
-                    Thread.sleep(forTimeInterval: 0.5)
+                while API.nGetCLXConnState() == 0 || !API.bInetConnAvailable() {
+                    Thread.sleep(forTimeInterval: 1)
                 }
-                if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.pullFloatingButton(), timeout: 30 * 1000){
+                if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.pullFloatingButton(), timeout: 5000) {
                     if response.isOk() {
                         Utils.setHistoryPullFB(value: response.getBody(key: CoreMessage_TMessageKey.DATA, default_value: ""))
                         setFBFromPull()

+ 68 - 19
NexilisLite/NexilisLite/Source/Nexilis.swift

@@ -117,6 +117,8 @@ public class Nexilis: NSObject {
     public static let IDX_SIGNUP_OR_IN_PAGE = 23
     public static let IDX_SECURE_FOLDER = 29
     public static let IDX_SETTING = 32
+    public static let IDX_WALLET = 72
+    public static let IDX_PPOB = 73
     public static let IDX_POST = 99
     public static let IDX_SELF_ACT = 100
     public static let IDX_SOCIAL_COMMERCE = 101
@@ -517,7 +519,6 @@ public class Nexilis: NSObject {
             return
         }
         isGettingFeatureAccess = true
-        print("getFeatureAccess")
         DispatchQueue.global(qos: .background).async {
 //            Utils.postDataWithCookiesAndUserAgent(from: URL(string: Utils.getDomainOpr() + "get_feature_access_new")!) { data, response, error in
 //                let response = response as? HTTPURLResponse
@@ -529,17 +530,24 @@ public class Nexilis: NSObject {
 //                    }
 //                }
 //            }
+            while Nexilis.isProcessWriteSync {
+                Thread.sleep(forTimeInterval: 0.5)
+            }
             if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.getFeatureAccessAll(), timeout: 5000), response.isOk() {
                 let data = response.getBody(key: CoreMessage_TMessageKey.DATA, default_value: "[]")
                 do {
                     if let data = data.data(using: .utf8) {
                         if let jsonArray = try JSONSerialization.jsonObject(with: data, options: []) as? [AnyObject] {
                             var jsonFA: [String: Any] = [:]
+                            var jsonFAWithAlert: [[String: Any]] = []
                             var keyTemp = ""
                             var keyIvTemp = ""
                             for jsonData in jsonArray {
                                 var tmp = jsonData as! [String: Any]
                                 tmp.removeValue(forKey: "action")
+                                if !tmp.keys.contains("secure_folder_encrypt_key") && !tmp.keys.contains("secure_folder_encrypt_key_iv") {
+                                    jsonFAWithAlert.append(tmp)
+                                }
                                 tmp.removeValue(forKey: "alert_title")
                                 tmp.removeValue(forKey: "alert_message")
                                 if Array(tmp.keys)[0] != "secure_folder_encrypt_key" && Array(tmp.keys)[0] != "secure_folder_encrypt_key_iv" {
@@ -613,6 +621,11 @@ public class Nexilis: NSObject {
                                     Utils.setFeatureAccess(value: jsonFAString)
                                 }
                             }
+                            if let convertJsonFAAlert = try? JSONSerialization.data(withJSONObject: jsonFAWithAlert, options: .prettyPrinted) {
+                                if let jsonFAString = String(data: convertJsonFAAlert, encoding: .utf8) {
+                                    Utils.setFeatureAccessAlert(value: jsonFAString)
+                                }
+                            }
                             isGettingFeatureAccess = false
                         }
                     }
@@ -657,6 +670,18 @@ public class Nexilis: NSObject {
         return false
     }
     
+    public static func checkingAccessAlert(key: String) -> String {
+        let dataAccess = Utils.getFeatureAccessAlert()
+        if let jsonArray = try? JSONSerialization.jsonObject(with: dataAccess.data(using: String.Encoding.utf8)!, options: []) as? [[String: Any]] {
+            if let indexKey = jsonArray.firstIndex(where: { $0.keys.contains(key) }) {
+                let title = jsonArray[indexKey]["alert_title"] as? String ?? ""
+                let message = jsonArray[indexKey]["alert_message"] as? String ?? ""
+                return "\(title)|\(message)"
+            }
+        }
+        return ""
+    }
+    
     private static func getPullWorkingArea() {
         while Nexilis.isProcessWriteSync {
             Thread.sleep(forTimeInterval: 0.5)
@@ -1082,13 +1107,15 @@ public class Nexilis: NSObject {
     
     public static var isProcessWriteSync = false
     public static func writeSync(message: TMessage, timeout: Int = 15 * 1000) -> TMessage? {
+        if !API.bInetConnAvailable() {
+            return nil
+        }
         isProcessWriteSync = true
         do {
 //            print(">> SENDING MESSAGE >> ", message.toLogString())
             if let data = try API.sGetResponse(sRequest: message.pack(), lTimeout: timeout, bKeepTOResp: true) {
                 let response = TMessage(data: data)
-//                print(">> RESPONSE WRITESYNC >> ")
-//                print("<< RESPONSE MESSAGE << ", response.toLogString())
+//                print("<< RESPONSE MESSAGE << ", data)
                 isProcessWriteSync = false
                 return response
             }
@@ -1174,7 +1201,31 @@ public class Nexilis: NSObject {
 
     public static func buttonClicked(index: Int, id: String = "") {
         //print("BTNCLICK \(index) \(id)")
-        if index == IDX_QUEUE_SYSTEM || index == IDX_NEWS || index == IDX_SOCIAL_COMMERCE {
+        if index == IDX_QUEUE_SYSTEM || index == IDX_NEWS || index == IDX_SOCIAL_COMMERCE || index == IDX_WALLET || index == IDX_PPOB {
+            if index == IDX_WALLET {
+                if !Nexilis.checkingAccess(key: "wallet") {
+                    if Nexilis.checkingAccessAlert(key: "wallet") != "|" && !Nexilis.checkingAccessAlert(key: "wallet").isEmpty {
+                        let title = Nexilis.checkingAccessAlert(key: "wallet").components(separatedBy: "|")[0]
+                        let message = Nexilis.checkingAccessAlert(key: "wallet").components(separatedBy: "|")[1]
+                        APIS.nexilisShowAlertWithHTMLMessage(on: UIApplication.shared.visibleViewController ?? UIViewController(), title: title, message: message)
+                    } else {
+                        UIApplication.shared.visibleViewController?.view.makeToast("Feature disabled".localized(), duration: 5)
+                    }
+                    return
+                }
+            }
+            if index == IDX_PPOB {
+                if !Nexilis.checkingAccess(key: "ppob") {
+                    if Nexilis.checkingAccessAlert(key: "ppob") != "|" && !Nexilis.checkingAccessAlert(key: "ppob").isEmpty {
+                        let title = Nexilis.checkingAccessAlert(key: "ppob").components(separatedBy: "|")[0]
+                        let message = Nexilis.checkingAccessAlert(key: "ppob").components(separatedBy: "|")[1]
+                        APIS.nexilisShowAlertWithHTMLMessage(on: UIApplication.shared.visibleViewController ?? UIViewController(), title: title, message: message)
+                    } else {
+                        UIApplication.shared.visibleViewController?.view.makeToast("Feature disabled".localized(), duration: 5)
+                    }
+                    return
+                }
+            }
             if id == "fb\(index)"{
                 APIS.openUrl(url: "https://google.com/")
             } else {
@@ -1344,21 +1395,22 @@ public class Nexilis: NSObject {
         loadingAlert.dismiss(animated: true, completion: completion)
     }
     
-    private static var groupWait = DispatchGroup()
-    
+    private static var listDispatchGroups = [String: DispatchGroup]()
     private static var waitQueue = [String: TMessage]()
     
-    private static var onDispatchGroupLeave = ""
-    
     public static func writeAndWait(message: TMessage, timeout: Int = 15 * 1000) -> TMessage? {
-        groupWait.enter()
-        _ = write(message: message, timeout: timeout)
+        listDispatchGroups[message.getStatus()] = DispatchGroup()
+        let groupWait = listDispatchGroups[message.getStatus()]
+        groupWait?.enter()
         waitQueue[message.getStatus()] = message
-        if groupWait.wait(timeout: .now() + 15) == .timedOut {
+        _ = write(message: message, timeout: timeout)
+        if groupWait?.wait(timeout: .now() + 15) == .timedOut {
             waitQueue.removeValue(forKey: message.getStatus())
-            groupWait.leave()
+            listDispatchGroups.removeValue(forKey: message.getStatus())
+            groupWait?.leave()
             return nil
         }
+        listDispatchGroups.removeValue(forKey: message.getStatus())
         return waitQueue.removeValue(forKey: message.getStatus())
     }
     
@@ -1411,13 +1463,10 @@ public class Nexilis: NSObject {
         if let _ = waitQueue[message.getStatus()] {
             //print("MESSAGE INCOMING DATA \(message.toLogString())")
             if message.mBodies.keys.contains(CoreMessage_TMessageKey.ERRCOD) {
-                if onDispatchGroupLeave != message.getStatus() {
-                    onDispatchGroupLeave = message.getStatus()
-                    //print("LEAVE GROUP INCOMING DATA")
-                    waitQueue[message.getStatus()] = message
-                    groupWait.leave()
-                    return
-                }
+                waitQueue[message.getStatus()] = message
+                let groupWait = listDispatchGroups[message.getStatus()]
+                groupWait?.leave()
+                return
             }
         }
         IncomingThread.default.addQueue(message: message)

+ 1 - 1
NexilisLite/NexilisLite/Source/TMessage.swift

@@ -34,7 +34,7 @@ public class TMessage {
     public static let TYPE_NEED_ACK    =  "3"
     
     public init() {
-        mVersion = "1.0.111"
+        mVersion = "1.0.116"
         mBodies[CoreMessage_TMessageKey.IMEI] = Nexilis.getCLMUserId()
 //        mBodies[CoreMessage_TMessageKey.VERCOD] = UIApplication.appVersion
         mBodies[CoreMessage_TMessageKey.VERCOD] = "2.2.177"

+ 11 - 0
NexilisLite/NexilisLite/Source/Utils.swift

@@ -351,6 +351,17 @@ public final class Utils {
         }
         return ""
     }
+    
+    static func setFeatureAccessAlert(value: String) {
+        SecureUserDefaults.shared.set(value, forKey: "pb_feature_access_alert")
+    }
+
+    static func getFeatureAccessAlert() -> String {
+        if let value: String = SecureUserDefaults.shared.value(forKey: "pb_feature_access_alert") {
+            return value
+        }
+        return ""
+    }
     static func setChatbotGreetings(value: String) {
         SecureUserDefaults.shared.set(value, forKey: "chatbot_greetings")
     }

+ 12 - 2
NexilisLite/NexilisLite/Source/View/Call/QmeraAudioViewController.swift

@@ -383,7 +383,7 @@ class QmeraAudioViewController: UIViewController {
 //                }
                 if callFCM {
                     DispatchQueue.global().async {
-                        if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.getCalling(fPin: u.pin, type: "1"), timeout: 30 * 1000) {
+                        if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getCalling(fPin: u.pin, type: "1"), timeout: 30 * 1000) {
                             if response.isOk() {
                                 DispatchQueue.main.async {
                                     self.status.text = "Waiting for answer".localized()
@@ -800,6 +800,16 @@ class QmeraAudioViewController: UIViewController {
     }
     
     @objc func didInvite(sender: Any?) {
+        if !Nexilis.checkingAccess(key: "audio_call_add") {
+            if Nexilis.checkingAccessAlert(key: "audio_call_add") != "|" && !Nexilis.checkingAccessAlert(key: "audio_call_add").isEmpty {
+                let title = Nexilis.checkingAccessAlert(key: "audio_call_add").components(separatedBy: "|")[0]
+                let message = Nexilis.checkingAccessAlert(key: "audio_call_add").components(separatedBy: "|")[1]
+                APIS.nexilisShowAlertWithHTMLMessage(on: UIApplication.shared.visibleViewController ?? UIViewController(), title: title, message: message)
+            } else {
+                UIApplication.shared.visibleViewController?.view.makeToast("Feature disabled".localized(), duration: 5)
+            }
+            return
+        }
         let controller = QmeraCallContactViewController()
         controller.isDismiss = { user in
             let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
@@ -814,7 +824,7 @@ class QmeraAudioViewController: UIViewController {
                 self.users.append(user)
                 if self.callFCM {
                     DispatchQueue.global().async {
-                        if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.getCalling(fPin: user.pin, type: "1"), timeout: 30 * 1000) {
+                        if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getCalling(fPin: user.pin, type: "1"), timeout: 30 * 1000) {
                             if response.isOk() {
                             } else if response.getBody(key: CoreMessage_TMessageKey.ERRCOD, default_value: "99") == "01" {
                                 API.initiateCCall(sParty: user.pin)

+ 13 - 3
NexilisLite/NexilisLite/Source/View/Call/QmeraVideoViewController.swift

@@ -264,7 +264,7 @@ class QmeraVideoViewController: UIViewController {
                 while API.nGetCLXConnState() == 0 {
                     Thread.sleep(forTimeInterval : 0.3)
                 }
-                _ = Nexilis.writeSync(message: CoreMessage_TMessageBank.getNotifyCalling(fPin: self.fPin, lPin: User.getMyPin()!, type: "2"))
+                _ = Nexilis.write(message: CoreMessage_TMessageBank.getNotifyCalling(fPin: self.fPin, lPin: User.getMyPin()!, type: "2"))
             }
         }
         self.timeStartCall = String(Date().currentTimeMillis())
@@ -500,7 +500,7 @@ class QmeraVideoViewController: UIViewController {
                 Nexilis.playRingbacktoneCall()
                 if callFCM {
                     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.writeAndWait(message: CoreMessage_TMessageBank.getCalling(fPin: self.dataPerson[0]["f_pin"]!!, type: "2"), timeout: 30 * 1000) {
                             if response.isOk() {
                                 DispatchQueue.main.async {
                                     self.labelIncomingOutgoing.text = "Waiting for answer".localized()
@@ -1167,6 +1167,16 @@ class QmeraVideoViewController: UIViewController {
     }
     
     @objc func didTapAddParticipantButton(sender: AnyObject){
+        if !Nexilis.checkingAccess(key: "video_call_add") {
+            if Nexilis.checkingAccessAlert(key: "video_call_add") != "|" && !Nexilis.checkingAccessAlert(key: "video_call_add").isEmpty {
+                let title = Nexilis.checkingAccessAlert(key: "video_call_add").components(separatedBy: "|")[0]
+                let message = Nexilis.checkingAccessAlert(key: "video_call_add").components(separatedBy: "|")[1]
+                APIS.nexilisShowAlertWithHTMLMessage(on: UIApplication.shared.visibleViewController ?? UIViewController(), title: title, message: message)
+            } else {
+                UIApplication.shared.visibleViewController?.view.makeToast("Feature disabled".localized(), duration: 5)
+            }
+            return
+        }
         if let contactViewController = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "contactSID") as? ContactCallViewController {
             contactViewController.isAddParticipantVideo = true
             contactViewController.connectedCall = dataPerson
@@ -1182,7 +1192,7 @@ class QmeraVideoViewController: UIViewController {
                 } else {
                     if self.callFCM {
                         DispatchQueue.global().async {
-                            if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.getCalling(fPin: data["f_pin"]!!, type: "2"), timeout: 30 * 1000) {
+                            if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getCalling(fPin: data["f_pin"]!!, type: "2"), timeout: 30 * 1000) {
                                 if response.isOk() {
                                 } else if response.getBody(key: CoreMessage_TMessageKey.ERRCOD, default_value: "99") == "01" {
                                     self.dataPerson.append(data)

+ 1 - 1
NexilisLite/NexilisLite/Source/View/Chat/ChatGPTBotView.swift

@@ -382,7 +382,7 @@ public class ChatGPTBotView: UIViewController, UIGestureRecognizerDelegate {
         }
         DispatchQueue.global().async {
             do {
-                if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.requestGPTBot(message: mesage)) {
+                if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.requestGPTBot(message: mesage)) {
                     if response.isOk() {
                         let data = response.getBody(key: CoreMessage_TMessageKey.DATA)
                         if let json = try! JSONSerialization.jsonObject(with: data.data(using: String.Encoding.utf8)!, options: []) as? [String: Any?] {

+ 165 - 0
NexilisLite/NexilisLite/Source/View/Chat/CommunityNew.swift

@@ -0,0 +1,165 @@
+//
+//  CommunityNew.swift
+//  Pods
+//
+//  Created by Qindi on 26/05/25.
+//
+
+import Foundation
+import UIKit
+
+public class CommunityNew: UIViewController {
+    
+    let containerButton = UIView()
+    let buttonComm = UIButton(type: .custom)
+    let scrollView = UIScrollView()
+    var bottomConstButton: NSLayoutConstraint!
+    let fieldName = UITextField()
+    
+    public override func viewDidLoad() {
+        setUpHeader()
+        view.backgroundColor = .whiteBubbleColor
+        setupScrollView()
+        
+        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow),
+                                                   name: UIResponder.keyboardWillShowNotification, object: nil)
+        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide),
+                                                   name: UIResponder.keyboardWillHideNotification, object: nil)
+        
+        fieldName.becomeFirstResponder()
+    }
+    
+    @objc func keyboardWillShow(notification: Notification) {
+        let info:NSDictionary = notification.userInfo! as NSDictionary
+        let keyboardSize = (info[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
+        let keyboardHeight: CGFloat = keyboardSize.height
+        let duration: CGFloat = info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat
+        let bottomInset = keyboardHeight
+
+        let contentInsets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight + 550, right: 0)
+        scrollView.contentInset = contentInsets
+        scrollView.scrollIndicatorInsets = contentInsets
+        bottomConstButton.constant = -bottomInset
+        var indicatorInsets = scrollView.verticalScrollIndicatorInsets
+        indicatorInsets.bottom = bottomInset
+        UIView.animate(withDuration: TimeInterval(duration), animations: {
+            self.view.layoutIfNeeded()
+        })
+        scrollView.verticalScrollIndicatorInsets = indicatorInsets
+        if let activeField = view.currentFirstResponder() {
+            scrollView.scrollRectToVisible(activeField.frame, animated: true)
+        }
+    }
+
+    @objc func keyboardWillHide(notification: Notification) {
+        let contentInsets = UIEdgeInsets.zero
+        scrollView.contentInset = contentInsets
+        scrollView.scrollIndicatorInsets = contentInsets
+        bottomConstButton.constant = 0
+    }
+    
+    func setUpHeader() {
+        navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(cancel(sender:)))
+        self.navigationController?.navigationController?.setNavigationBarHidden(true, animated: false)
+        self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: self.traitCollection.userInterfaceStyle == .dark ? .white : UIColor.black]
+        let attributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: self.traitCollection.userInterfaceStyle == .dark ? .white : UIColor.black, NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 16)]
+        let navBarAppearance = UINavigationBarAppearance()
+        navBarAppearance.configureWithTransparentBackground()
+        navBarAppearance.titleTextAttributes = attributes
+        navigationController?.navigationBar.standardAppearance = navBarAppearance
+        navigationController?.navigationBar.scrollEdgeAppearance = navBarAppearance
+        let cancelButtonAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: self.traitCollection.userInterfaceStyle == .dark ? .white : UIColor.black, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
+        UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes, for: .normal)
+        navigationController?.navigationBar.backgroundColor = .clear
+        navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
+        navigationController?.navigationBar.shadowImage = UIImage()
+        navigationController?.navigationBar.isTranslucent = true
+        navigationController?.setNavigationBarHidden(false, animated: false)
+        navigationController?.navigationBar.overrideUserInterfaceStyle = self.traitCollection.userInterfaceStyle == .dark ? .dark : .light
+        navigationController?.navigationBar.barStyle = .default
+        navigationController?.navigationBar.tintColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
+        tabBarController?.navigationItem.leftBarButtonItem = nil
+        tabBarController?.navigationItem.searchController = nil
+        
+        self.navigationController?.navigationBar.topItem?.title = "New Community".localized()
+        self.navigationController?.navigationBar.setNeedsLayout()
+    }
+    
+    func setupScrollView() {
+        scrollView.alwaysBounceHorizontal = false
+        scrollView.alwaysBounceVertical = true
+        scrollView.showsHorizontalScrollIndicator = false
+        scrollView.showsVerticalScrollIndicator = true
+        scrollView.isScrollEnabled = true
+        view.addSubview(scrollView)
+        scrollView.anchor(top: view.safeAreaLayoutGuide.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingBottom: 60)
+        
+        view.addSubview(containerButton)
+        containerButton.anchor(left: view.leftAnchor, right: view.rightAnchor, height: 60)
+        containerButton.backgroundColor = .whiteBubbleColor
+        bottomConstButton = containerButton.bottomAnchor.constraint(equalTo: view.bottomAnchor)
+        bottomConstButton.isActive = true
+        
+        let ppView = UIImageView()
+        ppView.backgroundColor = .whatsappGrayPPColor
+        scrollView.addSubview(ppView)
+        ppView.anchor(top: scrollView.topAnchor, paddingTop: 115, centerX: scrollView.centerXAnchor, width: 120, height: 120)
+        ppView.layer.cornerRadius = 25
+        ppView.clipsToBounds = true
+        ppView.image = UIImage(systemName: "person.3.fill")
+        ppView.tintColor = .white
+        ppView.contentMode = .scaleAspectFit
+        
+        let buttonAddPP = UIButton()
+        scrollView.addSubview(buttonAddPP)
+        buttonAddPP.setTitle("Add Photo".localized(), for: .normal)
+        buttonAddPP.setTitleColor(.whatsappGreenColor, for: .normal)
+        buttonAddPP.titleLabel?.font = .systemFont(ofSize: 16)
+        buttonAddPP.anchor(top: ppView.bottomAnchor, paddingTop: 5, centerX: scrollView.centerXAnchor)
+        
+        scrollView.addSubview(fieldName)
+        fieldName.font = .systemFont(ofSize: 14)
+        fieldName.placeholder = "Community Name".localized()
+        fieldName.backgroundColor = .white
+        fieldName.anchor(top: buttonAddPP.bottomAnchor, left: view.leftAnchor, right: view.rightAnchor, paddingTop: 10, paddingLeft: 20, paddingRight: 20, height: 45)
+        fieldName.layer.cornerRadius = 10
+        fieldName.clipsToBounds = true
+        fieldName.tintColor = .whatsappGreenColor
+        fieldName.textColor = .black
+        let leftPadding = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 0))
+        fieldName.leftView = leftPadding
+        fieldName.leftViewMode = .always
+        let rightPadding = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 0))
+        fieldName.rightView = rightPadding
+        fieldName.rightViewMode = .always
+        
+        let fieldDesc = UITextView()
+        scrollView.addSubview(fieldDesc)
+        fieldDesc.font = .systemFont(ofSize: 14)
+        fieldDesc.backgroundColor = .white
+        fieldDesc.anchor(top: fieldName.bottomAnchor, left: view.leftAnchor, right: view.rightAnchor, paddingTop: 20, paddingLeft: 20, paddingRight: 20, height: 135)
+        fieldDesc.layer.cornerRadius = 10
+        fieldDesc.clipsToBounds = true
+        fieldDesc.tintColor = .whatsappGreenColor
+        fieldDesc.textColor = .black
+        fieldDesc.textContainerInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
+        fieldDesc.textContainer.lineFragmentPadding = 0
+        fieldDesc.text = "Hi everyone! This community is for members to chat in topic-based groups and get important announcements.".localized()
+        
+        containerButton.addSubview(buttonComm)
+        buttonComm.anchor(top: containerButton.topAnchor, left: containerButton.leftAnchor, right: containerButton.rightAnchor, paddingTop: 5, paddingLeft: 30, paddingRight: 30, height: 45)
+//        buttonComm.backgroundColor = .whatsappGreenColor
+//        buttonComm.setTitleColor(.white, for: .normal)
+        buttonComm.backgroundColor = .waGrayLight
+        buttonComm.setTitleColor(.waGrayFont, for: .normal)
+        buttonComm.layer.cornerRadius = 15
+        buttonComm.clipsToBounds = true
+        buttonComm.setTitle("Create Community".localized(), for: .normal)
+        buttonComm.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
+        buttonComm.isEnabled = false
+    }
+    
+    @objc func cancel(sender: Any) {
+        self.dismiss(animated: true, completion: nil)
+    }
+}

+ 55 - 21
NexilisLite/NexilisLite/Source/View/Control/BackupRestoreView.swift

@@ -213,7 +213,9 @@ public class BackupRestoreView: UIViewController, UITableViewDataSource, UITable
             tableView.reloadRows(at: [indexPath], with: .none)
             tableView.reloadSections(IndexSet(integer: 2), with: .none)
             animateBackup()
-            backupData(indexPath: indexPath)
+            DispatchQueue.global().async {
+                self.backupData(indexPath: indexPath)
+            }
         } else if indexPath.section == 2 {
             if isBackupStart || isRestoreStart || valueLastBackup == "-" {
                 return
@@ -234,10 +236,7 @@ public class BackupRestoreView: UIViewController, UITableViewDataSource, UITable
             let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
             if let dirPath = paths.first {
                 let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(getFileName(option: optionBackup, fileId: fileIdBackup))
-                if FileManager.default.fileExists(atPath: fileURL.path) {
-                    labelRestoring.text = "Restoring...".localized()
-                    restoreData(file: fileURL, dirPath: dirPath, indexPath: indexPath)
-                } else if FileEncryption.shared.isSecureExists(filename: getFileName(option: optionBackup, fileId: fileIdBackup)) {
+                if FileEncryption.shared.isSecureExists(filename: getFileName(option: optionBackup, fileId: fileIdBackup)) {
                     do {
                         if var data = try FileEncryption.shared.readSecure(filename: getFileName(option: optionBackup, fileId: fileIdBackup)) {
                             let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
@@ -245,7 +244,9 @@ public class BackupRestoreView: UIViewController, UITableViewDataSource, UITable
                                 data = dataDecrypt!
                             }
                             try data.write(to: fileURL)
-                            restoreData(file: fileURL, dirPath: dirPath, indexPath: indexPath)
+                            DispatchQueue.global().async {
+                                self.restoreData(file: fileURL, dirPath: dirPath, indexPath: indexPath)
+                            }
                         }
                     } catch {
                         
@@ -267,7 +268,7 @@ public class BackupRestoreView: UIViewController, UITableViewDataSource, UITable
                                 return
                             }
                             labelRestoring.text = "Restoring...".localized()
-                            if FileEncryption.shared.isSecureExists(filename: fileIdBackup) {
+                            if FileEncryption.shared.isSecureExists(filename: getFileName(option: optionBackup, fileId: fileIdBackup)) {
                                 do {
                                     if var data = try FileEncryption.shared.readSecure(filename: getFileName(option: optionBackup, fileId: fileIdBackup)) {
                                         let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
@@ -275,7 +276,9 @@ public class BackupRestoreView: UIViewController, UITableViewDataSource, UITable
                                             data = dataDecrypt!
                                         }
                                         try data.write(to: fileURL)
-                                        restoreData(file: fileURL, dirPath: dirPath, indexPath: indexPath)
+                                        DispatchQueue.global().async {
+                                            self.restoreData(file: fileURL, dirPath: dirPath, indexPath: indexPath)
+                                        }
                                     }
                                 } catch {
                                     
@@ -487,18 +490,6 @@ public class BackupRestoreView: UIViewController, UITableViewDataSource, UITable
                     }
                 }
                 _ = try Database.shared.insertRecord(fmdb: fmdb, table: "MESSAGE", cvalues: cValues, replace: true)
-                if !(cValues["thumb_id"] as? String ?? "").isEmpty {
-                    let thumbId = cValues["thumb_id"] as! String
-                    let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
-                    let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
-                    let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
-                    if let dirPath = paths.first {
-                        let thumbURL = URL(fileURLWithPath: dirPath).appendingPathComponent(thumbId)
-                        if !FileManager.default.fileExists(atPath: thumbURL.path) {
-                            Download().startHTTP(forKey: thumbId) { (name, progress) in}
-                        }
-                    }
-                }
                 recordSizeRestore += 1
             } catch {
                 rollback.pointee = true
@@ -620,32 +611,52 @@ public class BackupRestoreView: UIViewController, UITableViewDataSource, UITable
                 if nameFile.trimmingCharacters(in: .whitespacesAndNewlines) == "MESSAGE" {
                     let nameColumn = valueText[0].components(separatedBy: separator)
                     for i in 1..<valueText.count - 1 {
+                        let percent = formatPercentage(numerator: recordSizeRestore, denominator: Int64(recordSizeBackup) ?? 0)
                         let dataMessage = valueText[i].components(separatedBy: separator)
                         restoreMessage(nameColumn: nameColumn, message: dataMessage)
+                        DispatchQueue.main.async { [self] in
+                            labelRestoring.text = "Restoring...".localized() + "  \(percent)"
+                        }
                     }
                 } else if nameFile.trimmingCharacters(in: .whitespacesAndNewlines) == "UC_LIST" {
                     for i in 1..<valueText.count - 1 {
+                        let percent = formatPercentage(numerator: recordSizeRestore, denominator: Int64(recordSizeBackup) ?? 0)
                         let dataUcList = valueText[i].components(separatedBy: separator)
                         restoreUcList(dataUcList: dataUcList)
+                        DispatchQueue.main.async { [self] in
+                            labelRestoring.text = "Restoring...".localized() + "  \(percent)"
+                        }
                     }
                 } else if nameFile.trimmingCharacters(in: .whitespacesAndNewlines) == "FORM_DATA" {
                     let nameColumn = valueText[0].components(separatedBy: separator)
                     for i in 1..<valueText.count - 1 {
+                        let percent = formatPercentage(numerator: recordSizeRestore, denominator: Int64(recordSizeBackup) ?? 0)
                         let dataFormData = valueText[i].components(separatedBy: separator)
                         restoreFormData(nameColumn: nameColumn, data: dataFormData)
+                        DispatchQueue.main.async { [self] in
+                            labelRestoring.text = "Restoring...".localized() + "  \(percent)"
+                        }
                     }
                 } else if nameFile.trimmingCharacters(in: .whitespacesAndNewlines) == "TASK_PIC" {
                     let nameColumn = valueText[0].components(separatedBy: separator)
                     for i in 1..<valueText.count - 1 {
+                        let percent = formatPercentage(numerator: recordSizeRestore, denominator: Int64(recordSizeBackup) ?? 0)
                         let dataTaskPIC = valueText[i].components(separatedBy: separator)
                         restoreTaskPIC(nameColumn: nameColumn, data: dataTaskPIC)
+                        DispatchQueue.main.async { [self] in
+                            labelRestoring.text = "Restoring...".localized() + "  \(percent)"
+                        }
                     }
                 }
                 else if nameFile.trimmingCharacters(in: .whitespacesAndNewlines) == "TASK_DETAIL" {
                     let nameColumn = valueText[0].components(separatedBy: separator)
                     for i in 1..<valueText.count - 1 {
+                        let percent = formatPercentage(numerator: recordSizeRestore, denominator: Int64(recordSizeBackup) ?? 0)
                         let dataTaskDetail = valueText[i].components(separatedBy: separator)
                         restoreTaskDetail(nameColumn: nameColumn, data: dataTaskDetail)
+                        DispatchQueue.main.async { [self] in
+                            labelRestoring.text = "Restoring...".localized() + "  \(percent)"
+                        }
                     }
                 }
             }
@@ -653,7 +664,9 @@ public class BackupRestoreView: UIViewController, UITableViewDataSource, UITable
                 labelRestoring.text = "Backup files are corrupted".localized()
                 tableView.reloadSections(IndexSet(integer: 3), with: .none)
             } else {
-                labelRestoring.text = "Successfully Restored Data".localized()
+                DispatchQueue.main.async { [self] in
+                    labelRestoring.text = "Successfully Restored Data".localized()
+                }
             }
 //            DispatchQueue.global().async { [self] in
 //                _ = Nexilis.write(message: CoreMessage_TMessageBank.getBackupRestored(option: optionBackup, fileid: fileIdBackup))
@@ -955,6 +968,13 @@ public class BackupRestoreView: UIViewController, UITableViewDataSource, UITable
                                         valueLastBackup = dayLastBackup.localized() + ", " + timeLastBackup
                                         valuesizeBackup = Units(bytes: fileSize).getReadableUnit()
                                         
+                                        do {
+                                            try FileEncryption.shared.writeSecure(filename: getFileName(option: optionBackup, fileId: fileIdBackup), data: Data(contentsOf: zipFiles))
+                                            try FileManager.default.removeItem(atPath: zipFiles.path)
+                                        } catch {
+                                            
+                                        }
+                                        
                                         labelPreparing.text = "Successfully Backup Data".localized()
                                         DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: { [self] in
                                             isBackupStart = false
@@ -987,6 +1007,20 @@ public class BackupRestoreView: UIViewController, UITableViewDataSource, UITable
 
 }
 
+func formatPercentage(numerator: Int64, denominator: Int64) -> String {
+    guard denominator != 0 else { return "NaN" }
+
+    let value = Double(numerator) / Double(denominator)
+
+    let formatter = NumberFormatter()
+    formatter.numberStyle = .percent
+    formatter.minimumFractionDigits = 1
+    formatter.maximumFractionDigits = 1
+    formatter.locale = Locale(identifier: "fr_FR") // uses comma as decimal separator
+
+    return formatter.string(from: NSNumber(value: value)) ?? "NaN"
+}
+
 extension String {
     func appendLineToURL(fileURL: URL) throws {
          try (self + "\n").appendToURL(fileURL: fileURL)

+ 1 - 0
NexilisLite/NexilisLite/Source/View/Control/ChangeDeviceViewController.swift

@@ -239,6 +239,7 @@ public class ChangeDeviceViewController: UIViewController {
             return
         }
         if Database.shared.openDatabase() == 0 {
+            APIS.showRestartApp()
             return
         }
         Nexilis.showLoader()

+ 1 - 0
NexilisLite/NexilisLite/Source/View/Control/SignUpSignIn.swift

@@ -268,6 +268,7 @@ public class SignUpSignIn: UIViewController {
             return
         }
         if Database.shared.openDatabase() == 0 {
+            APIS.showRestartApp()
             return
         }
         Nexilis.showLoader()

+ 1 - 1
StreamShield/StreamShield/Source/SecurityShield.swift

@@ -2231,7 +2231,7 @@ private class TMessageSS {
     }
     
     init() {
-        mVersion = "1.0.111"
+        mVersion = "1.0.116"
         mBodies[IMEI] = getCLMUserId()
         mBodies[VERCOD] = "2.2.177"
     }