Forráskód Böngészése

update fix bugs 5.0.28

alqindiirsyam 3 hónapja
szülő
commit
58ee70fca5
22 módosított fájl, 618 hozzáadás és 345 törlés
  1. 8 4
      AppBuilder/AppBuilder.xcodeproj/project.pbxproj
  2. 65 40
      AppBuilder/AppBuilder/FirstTabViewController.swift
  3. 1 0
      AppBuilder/AppBuilder/FourthTabViewController.swift
  4. 32 40
      AppBuilder/AppBuilder/PrefsUtil.swift
  5. 13 3
      AppBuilder/AppBuilder/SecondTabViewController.swift
  6. 66 41
      AppBuilder/AppBuilder/ThirdTabViewController.swift
  7. 16 4
      NexilisLite/NexilisLite/Source/APIS.swift
  8. 10 0
      NexilisLite/NexilisLite/Source/CoreMessage_TMessageBank.swift
  9. 1 0
      NexilisLite/NexilisLite/Source/CoreMessage_TMessageCode.swift
  10. 5 1
      NexilisLite/NexilisLite/Source/IncomingThread.swift
  11. 8 1
      NexilisLite/NexilisLite/Source/Nexilis.swift
  12. 1 1
      NexilisLite/NexilisLite/Source/Utils.swift
  13. 103 78
      NexilisLite/NexilisLite/Source/View/BNIView/BNIBookingWebView.swift
  14. 51 21
      NexilisLite/NexilisLite/Source/View/Call/CallManager.swift
  15. 24 34
      NexilisLite/NexilisLite/Source/View/Call/QmeraAudioViewController.swift
  16. 11 8
      NexilisLite/NexilisLite/Source/View/Call/QmeraVideoViewController.swift
  17. 13 3
      NexilisLite/NexilisLite/Source/View/Chat/ChatWALikeVC.swift
  18. 108 36
      NexilisLite/NexilisLite/Source/View/Chat/EditorGroup.swift
  19. 71 27
      NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift
  20. 7 2
      NexilisLite/NexilisLite/Source/View/Control/ContactChatViewController.swift
  21. 3 1
      NexilisLite/NexilisLite/Source/View/Control/QRScannerController.swift
  22. 1 0
      NexilisLite/NexilisLite/Source/View/Control/SettingTableViewController.swift

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

@@ -322,10 +322,14 @@
 			inputFileListPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-AppBuilder/Pods-AppBuilder-frameworks-${CONFIGURATION}-input-files.xcfilelist",
 			);
+			inputPaths = (
+			);
 			name = "[CP] Embed Pods Frameworks";
 			outputFileListPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-AppBuilder/Pods-AppBuilder-frameworks-${CONFIGURATION}-output-files.xcfilelist",
 			);
+			outputPaths = (
+			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
 			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AppBuilder/Pods-AppBuilder-frameworks.sh\"\n";
@@ -556,7 +560,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 5.0.7;
+				MARKETING_VERSION = 5.0.27;
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
@@ -592,7 +596,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 5.0.7;
+				MARKETING_VERSION = 5.0.27;
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
@@ -628,7 +632,7 @@
 					"@executable_path/../../Frameworks",
 				);
 				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
-				MARKETING_VERSION = 5.0.7;
+				MARKETING_VERSION = 5.0.27;
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder.AppBuilderShare;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SKIP_INSTALL = YES;
@@ -666,7 +670,7 @@
 					"@executable_path/../../Frameworks",
 				);
 				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
-				MARKETING_VERSION = 5.0.7;
+				MARKETING_VERSION = 5.0.27;
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder.AppBuilderShare;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SKIP_INSTALL = YES;

+ 65 - 40
AppBuilder/AppBuilder/FirstTabViewController.swift

@@ -726,62 +726,87 @@ extension FirstTabViewController: SFSpeechRecognizerDelegate {
 
 extension FirstTabViewController: WKUIDelegate, WKNavigationDelegate {
     func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping @MainActor (WKNavigationActionPolicy) -> Void) {
-        if navigationAction.targetFrame?.isMainFrame == true {
-            guard let url = navigationAction.request.url else {
-                decisionHandler(.cancel)
-                return
+        guard let url = navigationAction.request.url else {
+            decisionHandler(.cancel)
+            return
+        }
+        
+        if let scheme = url.scheme?.lowercased(), scheme.hasPrefix("itms") || scheme == "mailto" || scheme == "tel" {
+            DispatchQueue.main.async {
+                UIApplication.shared.open(url, options: [:], completionHandler: nil)
             }
-            if loadingURL {
+            decisionHandler(.cancel)
+            return
+        }
+
+        guard navigationAction.targetFrame?.isMainFrame == true else {
+            decisionHandler(.allow)
+            return
+        }
+
+        if loadingURL {
+            decisionHandler(.cancel)
+            return
+        }
+
+        loadingURL = true
+
+        if allowedURLs.contains(url.absoluteString) {
+            loadingURL = false
+            decisionHandler(.allow)
+            return
+        }
+        
+        validateSSLCertificate(url: url) { [weak self] isValid in
+            guard let self = self else {
                 decisionHandler(.cancel)
                 return
             }
-            loadingURL = true
-            if allowedURLs.contains(url.absoluteString) {
-                loadingURL = false
-                decisionHandler(.allow)
-                return
-            }
-            validateSSLCertificate(url: url) { isValid in
+
+            DispatchQueue.main.async {
                 if isValid {
                     self.allowedURLs.insert(url.absoluteString)
                     self.loadingURL = false
                     decisionHandler(.allow)
                 } else {
                     let host = url.host ?? ""
-                    DispatchQueue.main.async {
-                        var messageText = "You're about to access a website that is not currently trusted by your Nexilis Browser. This website's security certificate is not recognized.\n\nDo you wish to proceed to <<domain>> and trust the website's security certificate?\n\nNote: Adding a website to the trusted list may increase your risk of security vulnerability".localized()
-                        messageText = messageText.replacingOccurrences(of: "<<domain>>", with: host)
-                        let alert = UIAlertController(title: "Warning Unknown Url!".localized(),
-                                                      message: messageText,
-                                                      preferredStyle: .alert)
-
-                        let yesAction = UIAlertAction(title: "Yes", style: .default) { _ in
-                            let storedCertificate = Utils.getCertificatePinningWebview()
-                            if let jsonData = storedCertificate.data(using: .utf8),
-                               let certJson = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: String] {
-                                var certJson = certJson
-                                certJson[host] = self.blockedCertificate
-                                if let jsonData = try? JSONSerialization.data(withJSONObject: certJson, options: []),
-                                   let jsonString = String(data: jsonData, encoding: .utf8) {
-                                    Utils.setCertificatePinningWebview(value: jsonString)
-                                }
+                    var messageText = "You're about to access a website that is not currently trusted by your Nexilis Browser. This website's security certificate is not recognized.\n\nDo you wish to proceed to <<domain>> and trust the website's security certificate?\n\nNote: Adding a website to the trusted list may increase your risk of security vulnerability".localized()
+                    messageText = messageText.replacingOccurrences(of: "<<domain>>", with: host)
+
+                    let alert = UIAlertController(title: "Warning Unknown Url!".localized(),
+                                                  message: messageText,
+                                                  preferredStyle: .alert)
+
+                    alert.addAction(UIAlertAction(title: "Yes", style: .default) { _ in
+                        let storedCertificate = Utils.getCertificatePinningWebview()
+                        if let jsonData = storedCertificate.data(using: .utf8),
+                           let certJson = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: String] {
+                            var certJson = certJson
+                            certJson[host] = self.blockedCertificate
+                            if let jsonData = try? JSONSerialization.data(withJSONObject: certJson, options: []),
+                               let jsonString = String(data: jsonData, encoding: .utf8) {
+                                Utils.setCertificatePinningWebview(value: jsonString)
                             }
-                            self.allowedURLs.insert(url.absoluteString)
-                            self.loadingURL = false
-                            decisionHandler(.allow)
-                        }
-                        let noAction = UIAlertAction(title: "No", style: .cancel) { _ in
-                            self.loadingURL = false
-                            decisionHandler(.cancel)
                         }
-                        alert.addAction(yesAction)
-                        alert.addAction(noAction)
+
+                        self.allowedURLs.insert(url.absoluteString)
+                        self.loadingURL = false
+                        decisionHandler(.allow)
+                    })
+
+                    alert.addAction(UIAlertAction(title: "No", style: .cancel) { _ in
+                        self.loadingURL = false
+                        decisionHandler(.cancel)
+                    })
+
+                    if self.presentedViewController == nil {
                         self.present(alert, animated: true, completion: nil)
+                    } else {
+                        self.loadingURL = false
+                        decisionHandler(.cancel)
                     }
                 }
             }
-        } else {
-            decisionHandler(.cancel)
         }
     }
     

+ 1 - 0
AppBuilder/AppBuilder/FourthTabViewController.swift

@@ -817,6 +817,7 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
 //                            Nexilis.changeUser(f_pin: id)
                             SecureUserDefaults.shared.set(id, forKey: "me")
                             APIS.sendPushToken(Utils.getTokenAPN(), isResend: true)
+                            Nexilis.sendVersionToBE()
                             Utils.setProfile(value: false)
                             if Utils.getForceAnonymous() {
                                 self.deleteAllRecordDatabase()

+ 32 - 40
AppBuilder/AppBuilder/PrefsUtil.swift

@@ -128,49 +128,41 @@ class PrefsUtil {
         return value
     }
     
-    static let contentRulesAds = """
-    [
-        {
-            "trigger": { 
-                "url-filter": ".*(ads|adserver|advert|doubleclick|popads|popcash|onclickads|adfly|shorte|media.net|buysellads|revcontent).*", 
-                "resource-type": ["script", "iframe", "xmlhttprequest", "media"],
-                "unless-domain": ["nexilis.io"]
+    static let contentRulesAds = #"""
+        [
+          {
+            "trigger": {
+              "url-filter": "doubleclick.net"
             },
-            "action": { "type": "block" }
-        },
-        {
-            "trigger": { 
-                "url-filter": ".*(taboola|outbrain|scorecardresearch|googlesyndication|tracking|track|pixel|cpxinteractive|zedo|rubiconproject).*", 
-                "resource-type": ["script", "iframe", "xmlhttprequest"],
-                "unless-domain": ["nexilis.io"]
-            },
-            "action": { "type": "block" }
-        },
-        {
-            "trigger": { 
-                "url-filter": ".*(google-analytics|facebook.com/tr|analytics|gtag|data-collect|collect|hotjar|mixpanel|segment|mouseflow).*", 
-                "resource-type": ["script", "xmlhttprequest"],
-                "unless-domain": ["nexilis.io"]
+            "action": {
+              "type": "block"
+            }
+          },
+          {
+            "trigger": {
+              "url-filter": "googlesyndication.com"
             },
-            "action": { "type": "block" }
-        },
-        {
-            "trigger": { 
-                "url-filter": ".*(banner|sponsored|clicktrack|beacon|stats|metrics|impression|affiliate|clksite).*", 
-                "resource-type": ["script", "image", "iframe"],
-                "unless-domain": ["nexilis.io"]
+            "action": {
+              "type": "block"
+            }
+          },
+          {
+            "trigger": {
+              "url-filter": "taboola.com"
             },
-            "action": { "type": "block" }
-        },
-        {
-            "trigger": { 
-                "url-filter": ".*(interstitial|popunder|redirect|clickunder|popup|overlayad).*", 
-                "resource-type": ["script", "document", "subdocument"],
-                "unless-domain": ["nexilis.io"]
+            "action": {
+              "type": "block"
+            }
+          },
+          {
+            "trigger": {
+              "url-filter": "outbrain.com"
             },
-            "action": { "type": "block" }
-        }
-    ]
-    """
+            "action": {
+              "type": "block"
+            }
+          }
+        ]
+        """#
     
 }

+ 13 - 3
AppBuilder/AppBuilder/SecondTabViewController.swift

@@ -1773,8 +1773,7 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
             content.addSubview(imageView)
             imageView.translatesAutoresizingMaskIntoConstraints = false
             NSLayoutConstraint.activate([
-                imageView.topAnchor.constraint(equalTo: content.topAnchor, constant: 10.0),
-                imageView.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -10.0),
+                imageView.centerYAnchor.constraint(equalTo: content.centerYAnchor),
                 imageView.widthAnchor.constraint(equalToConstant: 55.0),
                 imageView.heightAnchor.constraint(equalToConstant: 55.0)
             ])
@@ -2152,7 +2151,12 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
                 stringURl = "https://" + stringURl.replacingOccurrences(of: "www.", with: "")
             }
             guard let url = URL(string: stringURl) else { return }
-            UIApplication.shared.open(url)
+            if Nexilis.checkingAccess(key: "secure_browser") {
+                APIS.openUrl(url: stringURl)
+            } else {
+                guard let url = URL(string: stringURl) else { return }
+                UIApplication.shared.open(url)
+            }
         } else if selectedTag == AUDIOS_TAG {
             let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
             let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
@@ -2220,6 +2224,12 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
             }
             return 130.0
         }
+        let fontSize = Int(SecureUserDefaults.shared.value(forKey: "font_size") ?? "0")
+        if fontSize == 4 {
+            return 85.0
+        } else if fontSize == 6 {
+            return 95.0
+        }
         return 75.0
     }
     

+ 66 - 41
AppBuilder/AppBuilder/ThirdTabViewController.swift

@@ -189,8 +189,8 @@ class ThirdTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
             } else {
                 self.webView.evaluateJavaScript("if(resumeAll){resumeAll();}")
             }
-            ThirdTabViewController.forceRefresh = false
         }
+        ThirdTabViewController.forceRefresh = false
     }
     
     override func viewWillAppear(_ animated: Bool) {
@@ -733,62 +733,87 @@ extension ThirdTabViewController: SFSpeechRecognizerDelegate {
 
 extension ThirdTabViewController: WKUIDelegate, WKNavigationDelegate {
     func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping @MainActor (WKNavigationActionPolicy) -> Void) {
-        if navigationAction.targetFrame?.isMainFrame == true {
-            guard let url = navigationAction.request.url else {
-                decisionHandler(.cancel)
-                return
+        guard let url = navigationAction.request.url else {
+            decisionHandler(.cancel)
+            return
+        }
+        
+        if let scheme = url.scheme?.lowercased(), scheme.hasPrefix("itms") || scheme == "mailto" || scheme == "tel" {
+            DispatchQueue.main.async {
+                UIApplication.shared.open(url, options: [:], completionHandler: nil)
             }
-            if loadingURL {
+            decisionHandler(.cancel)
+            return
+        }
+
+        guard navigationAction.targetFrame?.isMainFrame == true else {
+            decisionHandler(.allow)
+            return
+        }
+
+        if loadingURL {
+            decisionHandler(.cancel)
+            return
+        }
+
+        loadingURL = true
+
+        if allowedURLs.contains(url.absoluteString) {
+            loadingURL = false
+            decisionHandler(.allow)
+            return
+        }
+        
+        validateSSLCertificate(url: url) { [weak self] isValid in
+            guard let self = self else {
                 decisionHandler(.cancel)
                 return
             }
-            loadingURL = true
-            if allowedURLs.contains(url.absoluteString) {
-                loadingURL = false
-                decisionHandler(.allow)
-                return
-            }
-            validateSSLCertificate(url: url) { isValid in
+
+            DispatchQueue.main.async {
                 if isValid {
                     self.allowedURLs.insert(url.absoluteString)
                     self.loadingURL = false
                     decisionHandler(.allow)
                 } else {
                     let host = url.host ?? ""
-                    DispatchQueue.main.async {
-                        var messageText = "You're about to access a website that is not currently trusted by your Nexilis Browser. This website's security certificate is not recognized.\n\nDo you wish to proceed to <<domain>> and trust the website's security certificate?\n\nNote: Adding a website to the trusted list may increase your risk of security vulnerability".localized()
-                        messageText = messageText.replacingOccurrences(of: "<<domain>>", with: host)
-                        let alert = UIAlertController(title: "Warning Unknown Url!".localized(),
-                                                      message: messageText,
-                                                      preferredStyle: .alert)
-
-                        let yesAction = UIAlertAction(title: "Yes", style: .default) { _ in
-                            let storedCertificate = Utils.getCertificatePinningWebview()
-                            if let jsonData = storedCertificate.data(using: .utf8),
-                               let certJson = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: String] {
-                                var certJson = certJson
-                                certJson[host] = self.blockedCertificate
-                                if let jsonData = try? JSONSerialization.data(withJSONObject: certJson, options: []),
-                                   let jsonString = String(data: jsonData, encoding: .utf8) {
-                                    Utils.setCertificatePinningWebview(value: jsonString)
-                                }
+                    var messageText = "You're about to access a website that is not currently trusted by your Nexilis Browser. This website's security certificate is not recognized.\n\nDo you wish to proceed to <<domain>> and trust the website's security certificate?\n\nNote: Adding a website to the trusted list may increase your risk of security vulnerability".localized()
+                    messageText = messageText.replacingOccurrences(of: "<<domain>>", with: host)
+
+                    let alert = UIAlertController(title: "Warning Unknown Url!".localized(),
+                                                  message: messageText,
+                                                  preferredStyle: .alert)
+
+                    alert.addAction(UIAlertAction(title: "Yes", style: .default) { _ in
+                        let storedCertificate = Utils.getCertificatePinningWebview()
+                        if let jsonData = storedCertificate.data(using: .utf8),
+                           let certJson = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: String] {
+                            var certJson = certJson
+                            certJson[host] = self.blockedCertificate
+                            if let jsonData = try? JSONSerialization.data(withJSONObject: certJson, options: []),
+                               let jsonString = String(data: jsonData, encoding: .utf8) {
+                                Utils.setCertificatePinningWebview(value: jsonString)
                             }
-                            self.allowedURLs.insert(url.absoluteString)
-                            self.loadingURL = false
-                            decisionHandler(.allow)
-                        }
-                        let noAction = UIAlertAction(title: "No", style: .cancel) { _ in
-                            self.loadingURL = false
-                            decisionHandler(.cancel)
                         }
-                        alert.addAction(yesAction)
-                        alert.addAction(noAction)
+
+                        self.allowedURLs.insert(url.absoluteString)
+                        self.loadingURL = false
+                        decisionHandler(.allow)
+                    })
+
+                    alert.addAction(UIAlertAction(title: "No", style: .cancel) { _ in
+                        self.loadingURL = false
+                        decisionHandler(.cancel)
+                    })
+
+                    if self.presentedViewController == nil {
                         self.present(alert, animated: true, completion: nil)
+                    } else {
+                        self.loadingURL = false
+                        decisionHandler(.cancel)
                     }
                 }
             }
-        } else {
-            decisionHandler(.cancel)
         }
     }
     

+ 16 - 4
NexilisLite/NexilisLite/Source/APIS.swift

@@ -913,7 +913,7 @@ public class APIS: NSObject {
     public static func showNotificationNexilis(_ userInfo: [AnyHashable : Any]) {
         if checkAppStateisBackground() {
 //            Nexilis.sendStateToServer(s: "MASUK SHOW NOTIFICATION NEXILIS")
-            print("MASUK SHOW NOTIFICATION NEXILIS: \(userInfo)")
+//            print("MASUK SHOW NOTIFICATION NEXILIS: \(userInfo)")
             DispatchQueue.main.async {
                 if let payload = userInfo["payload"] as? [String: Any] {
                     if let messagePayload = payload["message"] as? [String: Any] {
@@ -1088,7 +1088,7 @@ public class APIS: NSObject {
                     if let data = data {
                         do {
                             if let dataString = String(data: data, encoding: .utf8) {
-                                if let jsonObj = try! JSONSerialization.jsonObject(with: dataString.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [String: Any] {
+                                if let jsonObj = try JSONSerialization.jsonObject(with: dataString.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [String: Any] {
                                     let dataObj = jsonObj["data"] as? String ?? ""
                                     let message = TMessage(data: dataObj)
                                     Nexilis.saveMessage(message: message, withStatus: false, fromAPNS: true)
@@ -1099,6 +1099,12 @@ public class APIS: NSObject {
                             
                         }
                     }
+                    DispatchQueue.main.async {
+                        UIApplication.shared.applicationIconBadgeNumber = Int(APIS.getTotalCounter())
+                    }
+                }
+                DispatchQueue.main.async {
+                    UIApplication.shared.applicationIconBadgeNumber = Int(APIS.getTotalCounter())
                 }
             }
 //            do {
@@ -1338,12 +1344,17 @@ public class APIS: NSObject {
                         var l_pin = ""
                         var message_scope_id = ""
                         var pin = ""
+                        var chat_id = ""
                         Database.shared.database?.inTransaction({ (fmdb, rollback) in
-                            if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select f_pin, l_pin, message_scope_id from MESSAGE where message_id = '\(message_id)'"), cursor.next() {
+                            if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select f_pin, l_pin, message_scope_id, chat_id from MESSAGE where message_id = '\(message_id)'"), cursor.next() {
                                 f_pin = cursor.string(forColumnIndex: 0) ?? ""
                                 l_pin = cursor.string(forColumnIndex: 1) ?? ""
                                 message_scope_id = cursor.string(forColumnIndex: 2) ?? ""
+                                chat_id = cursor.string(forColumnIndex: 3) ?? ""
                                 pin = f_pin == User.getMyPin() ? l_pin : f_pin
+                                if message_scope_id == "4" {
+                                    pin = chat_id.isEmpty ? l_pin : chat_id
+                                }
                                 cursor.close()
                             }
                         })
@@ -1352,6 +1363,7 @@ public class APIS: NSObject {
                                 navigationC.popViewController(animated: false)
                             }
                         }
+                        print("HUHU \(f_pin) \(l_pin) \(message_scope_id)")
                         showEditorOrCallFromAPN(pin, message_scope_id == "4" ? "1" : "0", "CL01")
                     }
                 }
@@ -1362,7 +1374,7 @@ public class APIS: NSObject {
     
     private static func showEditorOrCallFromAPN(_ id: String, _ type: String, _ callType: String) {
         if type == "0" {
-            if User.getDataCanNil(pin: id) == nil {
+            if User.getDataCanNil(pin: id) == nil && id != "-999" && id != "-997" {
                 return
             }
             let editorPersonalVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorPersonalVC") as! EditorPersonal

+ 10 - 0
NexilisLite/NexilisLite/Source/CoreMessage_TMessageBank.swift

@@ -2679,4 +2679,14 @@ public class CoreMessage_TMessageBank {
         return tMessage
     }
     
+    public static func updateVersion() -> TMessage {
+        let tMessage = NexilisLite.TMessage()
+        let me = User.getMyPin() ?? ""
+        tMessage.mPIN = me
+        tMessage.mCode = CoreMessage_TMessageCode.UPDATE_VERSION
+        tMessage.mStatus = CoreMessage_TMessageUtil.getTID()
+        tMessage.mBodies[CoreMessage_TMessageKey.VERSION] = UIApplication.appVersion
+        return tMessage
+    }
+    
 }

+ 1 - 0
NexilisLite/NexilisLite/Source/CoreMessage_TMessageCode.swift

@@ -805,4 +805,5 @@ public class CoreMessage_TMessageCode {
     public static let GPT_SERVICE = "GPTS";
     
     public static let GET_PUSH_PREFS = "GPR";
+    public static let UPDATE_VERSION = "UPV";
 }

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

@@ -241,6 +241,7 @@ class IncomingThread {
 //                            Nexilis.changeUser(f_pin: id)
                     SecureUserDefaults.shared.set(id, forKey: "me")
                     APIS.sendPushToken(Utils.getTokenAPN(), isResend: true)
+                    Nexilis.sendVersionToBE()
                     Utils.setProfile(value: false)
                     if Utils.getForceAnonymous() {
                         viewController?.deleteAllRecordDatabase()
@@ -563,7 +564,7 @@ class IncomingThread {
                                 Nexilis.shared.floating.dismiss()
                             }
                             Nexilis.shared.floating = FloatingNotificationBanner(customView: container)
-                            Nexilis.shared.floating.bannerHeight = 100.0
+                            Nexilis.shared.floating.bannerHeight = UIScreen.main.bounds.height / 6 - 10
                             Nexilis.shared.floating.transparency = 0.9
                             
                             let profile = CoreMessage_TMessageUtil.getString(json: json, key: CoreMessage_TMessageKey.THUMB_ID)
@@ -1480,6 +1481,7 @@ class IncomingThread {
                         if User.getMyPin() != cursorUser.string(forColumnIndex: 0) {
                             SecureUserDefaults.shared.set(cursorUser.string(forColumnIndex: 0), forKey: "me")
                             APIS.sendPushToken(Utils.getTokenAPN(), isResend: true)
+                            Nexilis.sendVersionToBE()
                         }
                         cursorUser.close()
                     }
@@ -1590,6 +1592,7 @@ class IncomingThread {
                     if User.getMyPin() != cursorUser.string(forColumnIndex: 0) {
                         SecureUserDefaults.shared.set(cursorUser.string(forColumnIndex: 0), forKey: "me")
                         APIS.sendPushToken(Utils.getTokenAPN(), isResend: true)
+                        Nexilis.sendVersionToBE()
                     }
                     cursorUser.close()
                 }
@@ -2093,6 +2096,7 @@ class IncomingThread {
             let f_pin = message.getBody(key: CoreMessage_TMessageKey.F_PIN, default_value: "00")
             SecureUserDefaults.shared.set(f_pin, forKey: "me")
             APIS.sendPushToken(Utils.getTokenAPN(), isResend: true)
+            Nexilis.sendVersionToBE()
             if let delegate = Nexilis.shared.loginDelegate {
                 delegate.onProcess(message: f_pin, status: message.getBody(key: CoreMessage_TMessageKey.ERRCOD, default_value: "00"))
             }

+ 8 - 1
NexilisLite/NexilisLite/Source/Nexilis.swift

@@ -204,6 +204,7 @@ public class Nexilis: NSObject {
                     SecureUserDefaults.shared.set(apiKey, forKey: "apiKey")
                 }
                 
+                sendVersionToBE()
                 getPullPrefs()
                 getFeatureAccess()
                 
@@ -394,6 +395,12 @@ public class Nexilis: NSObject {
         }
     }
     
+    public static func sendVersionToBE() {
+        DispatchQueue.global().async {
+            _ = Nexilis.write(message: CoreMessage_TMessageBank.updateVersion())
+        }
+    }
+    
     private static func getPullPrefs() {
         DispatchQueue.global().async {
             let urlString = Utils.getBEId().isEmpty ? Utils.getDomainOpr() + "nexilis/logics/get_baseurl_new?key=\(Nexilis.sAPIKey)" : Utils.getDomainOpr() + "nexilis/logics/get_prefs?be=\(Utils.getBEId())&appId=\(APIS.getAppNm())"
@@ -3895,7 +3902,7 @@ extension Nexilis: MessageDelegate {
                     
                     func displayNotif() {
                         floating = FloatingNotificationBanner(customView: container)
-                        floating.bannerHeight = 100.0
+                        floating.bannerHeight = UIScreen.main.bounds.height / 6 - 10
                         floating.transparency = 0.9
                         
                         if threadIdentifier == "-999" {

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

@@ -502,7 +502,7 @@ public final class Utils {
             }
             let nameFile = chat.messageText.components(separatedBy: "|")[0]
             let dataText = chat.messageText.components(separatedBy: "|")[1]
-            if !dataText.isEmpty {
+            if !dataText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
                 return ("📄 " + dataText).richText(group_id: chat.pin)
             }
             return showNSMutableAttributedString(("📄 \(nameFile)"))

+ 103 - 78
NexilisLite/NexilisLite/Source/View/BNIView/BNIBookingWebView.swift

@@ -177,7 +177,7 @@ public class BNIBookingWebView: UIViewController, WKNavigationDelegate, UIScroll
         if lang == "id" {
             intLang = 1
         }
-        if stringQMS.starts(with: Utils.getURLBase()) {
+        if stringQMS.starts(with: Utils.getURLBase()) && !stringQMS.contains("/TrustedChannel") && !stringQMS.contains("/get_oneapp") {
             stringQMS = stringQMS + "&lang=\(intLang)&theme=\(self.traitCollection.userInterfaceStyle == .dark ? "0" : "1")"
         }
         if let url = URL(string: "\(stringQMS)") {
@@ -197,50 +197,42 @@ public class BNIBookingWebView: UIViewController, WKNavigationDelegate, UIScroll
     
     func loadContentBlocker(into config: WKWebViewConfiguration, completion: @escaping () -> Void) {
         // Define ad-blocking rules directly in Swift as a string
-        let contentRules = """
+        let contentRules = #"""
         [
-            {
-                "trigger": { 
-                    "url-filter": ".*(ads|adserver|advert|doubleclick|popads|popcash|onclickads|adfly|shorte|media.net|buysellads|revcontent).*", 
-                    "resource-type": ["script", "iframe", "xmlhttprequest", "media"],
-                    "unless-domain": ["nexilis.io"]
-                },
-                "action": { "type": "block" }
+          {
+            "trigger": {
+              "url-filter": "doubleclick.net"
             },
-            {
-                "trigger": { 
-                    "url-filter": ".*(taboola|outbrain|scorecardresearch|googlesyndication|tracking|track|pixel|cpxinteractive|zedo|rubiconproject).*", 
-                    "resource-type": ["script", "iframe", "xmlhttprequest"],
-                    "unless-domain": ["nexilis.io"]
-                },
-                "action": { "type": "block" }
+            "action": {
+              "type": "block"
+            }
+          },
+          {
+            "trigger": {
+              "url-filter": "googlesyndication.com"
             },
-            {
-                "trigger": { 
-                    "url-filter": ".*(google-analytics|facebook.com/tr|analytics|gtag|data-collect|collect|hotjar|mixpanel|segment|mouseflow).*", 
-                    "resource-type": ["script", "xmlhttprequest"],
-                    "unless-domain": ["nexilis.io"]
-                },
-                "action": { "type": "block" }
+            "action": {
+              "type": "block"
+            }
+          },
+          {
+            "trigger": {
+              "url-filter": "taboola.com"
             },
-            {
-                "trigger": { 
-                    "url-filter": ".*(banner|sponsored|clicktrack|beacon|stats|metrics|impression|affiliate|clksite).*", 
-                    "resource-type": ["script", "image", "iframe"],
-                    "unless-domain": ["nexilis.io"]
-                },
-                "action": { "type": "block" }
+            "action": {
+              "type": "block"
+            }
+          },
+          {
+            "trigger": {
+              "url-filter": "outbrain.com"
             },
-            {
-                "trigger": { 
-                    "url-filter": ".*(interstitial|popunder|redirect|clickunder|popup|overlayad).*", 
-                    "resource-type": ["script", "document", "subdocument"],
-                    "unless-domain": ["nexilis.io"]
-                },
-                "action": { "type": "block" }
+            "action": {
+              "type": "block"
             }
+          }
         ]
-        """
+        """#
 
         WKContentRuleListStore.default().compileContentRuleList(forIdentifier: "AdBlocker", encodedContentRuleList: contentRules) { ruleList, error in
             if let ruleList = ruleList {
@@ -847,62 +839,95 @@ public class BNIBookingWebView: UIViewController, WKNavigationDelegate, UIScroll
     }
     
     public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping @MainActor (WKNavigationActionPolicy) -> Void) {
-        if navigationAction.targetFrame?.isMainFrame == true {
-            guard let url = navigationAction.request.url else {
-                decisionHandler(.cancel)
-                return
+        guard let url = navigationAction.request.url else {
+//            print("return 0")
+            decisionHandler(.cancel)
+            return
+        }
+        
+        if let scheme = url.scheme?.lowercased(), scheme.hasPrefix("itms") || scheme == "mailto" || scheme == "tel" {
+            DispatchQueue.main.async {
+                UIApplication.shared.open(url, options: [:], completionHandler: nil)
             }
-            if loadingURL {
+//            print("return 1 \(url.absoluteString)")
+            decisionHandler(.cancel)
+            return
+        }
+
+        guard navigationAction.targetFrame?.isMainFrame == true else {
+            decisionHandler(.allow)
+//            print("return 2 \(url.absoluteString)")
+            return
+        }
+
+        if loadingURL {
+            decisionHandler(.cancel)
+//            print("return 3 \(url.absoluteString)")
+            return
+        }
+
+        loadingURL = true
+
+        if allowedURLs.contains(url.absoluteString) {
+            loadingURL = false
+            decisionHandler(.allow)
+//            print("return 4 \(url.absoluteString)")
+            return
+        }
+        
+        validateSSLCertificate(url: url) { [weak self] isValid in
+            guard let self = self else {
+//                print("return 5 \(url.absoluteString)")
                 decisionHandler(.cancel)
                 return
             }
-            loadingURL = true
-            if allowedURLs.contains(url.absoluteString) {
-                loadingURL = false
-                decisionHandler(.allow)
-                return
-            }
-            validateSSLCertificate(url: url) { isValid in
+
+            DispatchQueue.main.async {
                 if isValid {
                     self.allowedURLs.insert(url.absoluteString)
                     self.loadingURL = false
+//                    print("return 6 \(url.absoluteString)")
                     decisionHandler(.allow)
                 } else {
                     let host = url.host ?? ""
-                    DispatchQueue.main.async {
-                        var messageText = "You're about to access a website that is not currently trusted by your Nexilis Browser. This website's security certificate is not recognized.\n\nDo you wish to proceed to <<domain>> and trust the website's security certificate?\n\nNote: Adding a website to the trusted list may increase your risk of security vulnerability".localized()
-                        messageText = messageText.replacingOccurrences(of: "<<domain>>", with: host)
-                        let alert = UIAlertController(title: "Warning Unknown Url!".localized(),
-                                                      message: messageText,
-                                                      preferredStyle: .alert)
-
-                        let yesAction = UIAlertAction(title: "Yes", style: .default) { _ in
-                            let storedCertificate = Utils.getCertificatePinningWebview()
-                            if let jsonData = storedCertificate.data(using: .utf8),
-                               let certJson = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: String] {
-                                var certJson = certJson
-                                certJson[host] = self.blockedCertificate
-                                if let jsonData = try? JSONSerialization.data(withJSONObject: certJson, options: []),
-                                   let jsonString = String(data: jsonData, encoding: .utf8) {
-                                    Utils.setCertificatePinningWebview(value: jsonString)
-                                }
+                    var messageText = "You're about to access a website that is not currently trusted by your Nexilis Browser. This website's security certificate is not recognized.\n\nDo you wish to proceed to <<domain>> and trust the website's security certificate?\n\nNote: Adding a website to the trusted list may increase your risk of security vulnerability".localized()
+                    messageText = messageText.replacingOccurrences(of: "<<domain>>", with: host)
+
+                    let alert = UIAlertController(title: "Warning Unknown Url!".localized(),
+                                                  message: messageText,
+                                                  preferredStyle: .alert)
+
+                    alert.addAction(UIAlertAction(title: "Yes", style: .default) { _ in
+                        let storedCertificate = Utils.getCertificatePinningWebview()
+                        if let jsonData = storedCertificate.data(using: .utf8),
+                           let certJson = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: String] {
+                            var certJson = certJson
+                            certJson[host] = self.blockedCertificate
+                            if let jsonData = try? JSONSerialization.data(withJSONObject: certJson, options: []),
+                               let jsonString = String(data: jsonData, encoding: .utf8) {
+                                Utils.setCertificatePinningWebview(value: jsonString)
                             }
-                            self.allowedURLs.insert(url.absoluteString)
-                            self.loadingURL = false
-                            decisionHandler(.allow)
                         }
-                        let noAction = UIAlertAction(title: "No", style: .cancel) { _ in
-                            self.loadingURL = false
-                            decisionHandler(.cancel)
-                        }
-                        alert.addAction(yesAction)
-                        alert.addAction(noAction)
+
+                        self.allowedURLs.insert(url.absoluteString)
+                        self.loadingURL = false
+                        decisionHandler(.allow)
+                    })
+
+                    alert.addAction(UIAlertAction(title: "No", style: .cancel) { _ in
+                        self.loadingURL = false
+                        decisionHandler(.cancel)
+                    })
+
+                    if self.presentedViewController == nil {
                         self.present(alert, animated: true, completion: nil)
+                    } else {
+                        self.loadingURL = false
+//                        print("return 7 \(url.absoluteString)")
+                        decisionHandler(.cancel)
                     }
                 }
             }
-        } else {
-            decisionHandler(.cancel)
         }
     }
     

+ 51 - 21
NexilisLite/NexilisLite/Source/View/Call/CallManager.swift

@@ -8,6 +8,9 @@
 import Foundation
 import CallKit
 import nuSDKService
+import AVFAudio
+import UIKit
+import Combine
 
 public class CallManager: NSObject, ObservableObject {
     
@@ -64,6 +67,10 @@ extension CallManager: CXProviderDelegate {
     }
     
     public func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
+//        let audioSession = AVAudioSession.sharedInstance()
+//        try? audioSession.setCategory(.playAndRecord, mode: .default, options: [.allowBluetooth, .defaultToSpeaker])
+//        try? audioSession.setActive(true)
+//        
         let uuid = action.callUUID
         if let callInfo = activeCalls[uuid] {
             self.activeCalls[uuid]?.isAccepted = true
@@ -107,6 +114,11 @@ extension CallManager: CXProviderDelegate {
                     })
                     return
                 }
+//                let vc = UIViewController()
+//                vc.modalPresentationStyle = .fullScreen
+//                if #available(iOS 15.0, *) {
+//                    rootWindowScene()?.keyWindow?.topVC().present(vc, animated: false)
+//                }
                 if UIApplication.shared.visibleViewController?.navigationController != nil {
                     UIApplication.shared.visibleViewController?.navigationController?.present(controller, animated: true, completion: nil)
                 } else {
@@ -117,6 +129,10 @@ extension CallManager: CXProviderDelegate {
         action.fulfill()
     }
     
+    private func rootWindowScene() -> UIWindowScene? {
+        UIApplication.shared.connectedScenes.compactMap{$0 as? UIWindowScene}.first{$0.activationState == .foregroundActive}
+    }
+    
     public func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
         if Nexilis.callAPNActivated {
             let uuid = action.callUUID
@@ -135,29 +151,26 @@ extension CallManager: CXProviderDelegate {
                                 sendCancel()
                             }
                             func sendCancel() {
-                                if let result = Nexilis.writeSync(message: CoreMessage_TMessageBank.getCancelCall(fPin: callInfo.callerId, type: !callInfo.isVideo ? "1" : "2"), timeout: 30 * 1000) {
-                                    if result.isOk() {
-                                        let center = UNUserNotificationCenter.current()
-                                        var textCall = ""
-                                        if !callInfo.isVideo {
-                                            textCall = "audio"
-                                        } else {
-                                            textCall = "video"
-                                        }
-                                        let content = UNMutableNotificationContent()
-                                        content.title = callInfo.callerName
-                                        content.body = "☎️ Missed \(textCall) call".localized()
-                                        content.userInfo = ["id" : callInfo.callerId, "type" : "CL02", "callType": callInfo.isVideo ? "2" : "1"]
-                                        content.sound = nil
-                                        let request = UNNotificationRequest(identifier: callInfo.callerId, content: content, trigger: nil)
-                                        center.add(request) { error in
-                                            if let error = error {
-                                                print("Error scheduling notification: \(error.localizedDescription)")
-                                            }
-                                        }
-                                        Nexilis.saveMessageCall(idCall: (User.getMyPin() ?? "") + CoreMessage_TMessageUtil.getTID(), textMessage: "Missed \(textCall) call".localized() + " at 0", fPin: callInfo.callerId, lPin: (User.getMyPin() ?? ""), timeCall: String(Date().currentTimeMillis()), attachment_type: MessageScope.MISSED_CALL)
+                                _ = Nexilis.write(message: CoreMessage_TMessageBank.getCancelCall(fPin: callInfo.callerId, type: !callInfo.isVideo ? "1" : "2"))
+                                let center = UNUserNotificationCenter.current()
+                                var textCall = ""
+                                if !callInfo.isVideo {
+                                    textCall = "audio"
+                                } else {
+                                    textCall = "video"
+                                }
+                                let content = UNMutableNotificationContent()
+                                content.title = callInfo.callerName
+                                content.body = "☎️ Missed \(textCall) call".localized()
+                                content.userInfo = ["id" : callInfo.callerId, "type" : "CL02", "callType": callInfo.isVideo ? "2" : "1"]
+                                content.sound = nil
+                                let request = UNNotificationRequest(identifier: callInfo.callerId, content: content, trigger: nil)
+                                center.add(request) { error in
+                                    if let error = error {
+                                        print("Error scheduling notification: \(error.localizedDescription)")
                                     }
                                 }
+                                Nexilis.saveMessageCall(idCall: (User.getMyPin() ?? "") + CoreMessage_TMessageUtil.getTID(), textMessage: "Missed \(textCall) call".localized() + " at 0", fPin: callInfo.callerId, lPin: (User.getMyPin() ?? ""), timeCall: String(Date().currentTimeMillis()), attachment_type: MessageScope.MISSED_CALL)
                             }
                         } catch {
                             
@@ -165,6 +178,13 @@ extension CallManager: CXProviderDelegate {
                     }
                     APIS.uuidCall = nil
                     Nexilis.callAPNActivated = false
+                } else {
+                    DispatchQueue.main.async {
+                        if APIS.checkAppStateisBackground() {
+                            do { try AVAudioSession.sharedInstance().setActive(false) } catch {}
+                            API.terminateCall(sParty: nil)
+                        }
+                    }
                 }
             }
         }
@@ -172,6 +192,16 @@ extension CallManager: CXProviderDelegate {
     }
 }
 
+private extension UIWindow {
+    func topVC() -> UIViewController {
+        var top = rootViewController!
+        while let next = top.presentedViewController {
+            top = next
+        }
+        return top
+    }
+}
+
 struct CallInfo {
     let uuid: UUID
     let callerId: String

+ 24 - 34
NexilisLite/NexilisLite/Source/View/Call/QmeraAudioViewController.swift

@@ -263,7 +263,6 @@ class QmeraAudioViewController: UIViewController {
     }()
     
     static func turnSpeakerOn() {
-        bSpeakerPhone = !bSpeakerPhone
         var bAudioEngineIsAvtive: Bool! = false
         API.turnSpeakerPhone(bSPon: bSpeakerPhone)
         repeat {
@@ -277,10 +276,14 @@ class QmeraAudioViewController: UIViewController {
         } while (!bAudioEngineIsAvtive)
         var volume:Float! = 0
         if (bSpeakerPhone) {
-            UIDevice.current.isProximityMonitoringEnabled = false
+            DispatchQueue.main.async {
+                UIDevice.current.isProximityMonitoringEnabled = false
+            }
             volume = lastVolume * nMaxSPOn
         } else {
-            UIDevice.current.isProximityMonitoringEnabled = true
+            DispatchQueue.main.async {
+                UIDevice.current.isProximityMonitoringEnabled = true
+            }
             volume = lastVolume * nMaxSPOff
         }
         API.adjustVolume(fValue: volume)
@@ -512,7 +515,7 @@ class QmeraAudioViewController: UIViewController {
     private func resetViewToOutgoing() {
         self.timer?.invalidate()
         self.timer = nil
-        self.firstCall = false
+        self.firstCall = true
         status.removeFromSuperview()
         profiles.removeFromSuperview()
         name.removeFromSuperview()
@@ -646,7 +649,6 @@ class QmeraAudioViewController: UIViewController {
         poweredByView.addArrangedSubview(nexilisLogo)
         stackViewToolbar2.addArrangedSubview(buttonWB)
         stackViewToolbar2.addArrangedSubview(buttonChat)
-        
     }
     
     
@@ -756,8 +758,11 @@ class QmeraAudioViewController: UIViewController {
     }
     
     @objc func didSpeaker(sender: Any?) {
-        QmeraAudioViewController.turnSpeakerOn()
+        QmeraAudioViewController.bSpeakerPhone = !QmeraAudioViewController.bSpeakerPhone
         speaker.isSelected = QmeraAudioViewController.bSpeakerPhone
+        DispatchQueue.global().async {
+            QmeraAudioViewController.turnSpeakerOn()
+        }
     }
     
     @objc func didMute(sender: Any?) {
@@ -856,6 +861,11 @@ class QmeraAudioViewController: UIViewController {
             Nexilis.stopRingtoneCall()
             Nexilis.stopRingbacktoneCall()
         }
+        if APIS.uuidCall != nil {
+            CallManager.shared.endCall(uuid: APIS.uuidCall!) {
+                APIS.uuidCall = nil
+            }
+        }
         poweredByView.isHidden = true
         let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
         if !onGoingCC.isEmpty {
@@ -1042,6 +1052,10 @@ class QmeraAudioViewController: UIViewController {
 //                        }
 //                    } while (QmeraAudioViewController.isLoop)
 //                }
+                DispatchQueue.main.async { [self] in
+                    QmeraAudioViewController.bSpeakerPhone = true
+                    didSpeaker(sender: nil)
+                }
             } else if state == Nexilis.AUDIO_CALL_RINGING || (!ticketId.isEmpty && state == Nexilis.VIDEO_CALL_RINGING) {
                 if users.count == 1 && !autoAcceptAPN {
                     DispatchQueue.main.async {
@@ -1049,12 +1063,12 @@ class QmeraAudioViewController: UIViewController {
                     }
                 }
             } else if state == Nexilis.AUDIO_CALL_OFFHOOK || (!ticketId.isEmpty && state == Nexilis.VIDEO_CALL_OFFHOOK) {
-                DispatchQueue.main.async {
-                    Nexilis.stopRingbacktoneCall()
-                }
+//                DispatchQueue.main.async {
+//                    Nexilis.stopRingbacktoneCall()
+//                }
                 if users.count == 1 && firstCall {
                     DispatchQueue.main.async {
-                        if !self.ticketId.isEmpty {
+                        if !self.ticketId.isEmpty || (self.timer == nil && !self.stack.isDescendant(of: self.view)) {
                             NSLayoutConstraint.deactivate(self.stack.constraints)
                             self.stack.subviews.forEach { subview in
                                 subview.removeFromSuperview()
@@ -1077,30 +1091,6 @@ class QmeraAudioViewController: UIViewController {
                     self.users.append(user)
                 }
                 users.forEach({ $0.isConnected = true })
-            } else if state == Nexilis.OUTGOING_CALL {
-                DispatchQueue.main.async {
-                    if self.timer == nil && !self.stack.isDescendant(of: self.view) {
-                        DispatchQueue.main.async {
-                            if !self.ticketId.isEmpty {
-                                NSLayoutConstraint.deactivate(self.stack.constraints)
-                                self.stack.subviews.forEach { subview in
-                                    subview.removeFromSuperview()
-                                }
-                                UIView.animate(withDuration: 0.3, animations: {
-                                    self.view.layoutIfNeeded()
-                                })
-                            }
-                            self.ongoingView()
-                            let connectDate = Date()
-                            self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
-                                let format = Utils.callDurationFormatter.string(from: Date().timeIntervalSince(connectDate))
-                                self.status.text = format
-                            }
-                            self.timer?.fire()
-                            self.firstCall = false
-                        }
-                    }
-                }
             } else if state == Nexilis.AUDIO_CALL_END || (!ticketId.isEmpty && state == Nexilis.VIDEO_CALL_END) {
                 DispatchQueue.main.async {
                     if let sharedAudioPlayer = Nexilis.sharedAudioPlayer, sharedAudioPlayer.isPlaying {

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

@@ -144,7 +144,6 @@ class QmeraVideoViewController: UIViewController {
     }()
     
     static func turnSpeakerOn() {
-        bSpeakerPhone = !bSpeakerPhone
         var bAudioEngineIsAvtive: Bool! = false
         API.turnSpeakerPhone(bSPon: bSpeakerPhone)
         repeat {
@@ -778,9 +777,6 @@ class QmeraVideoViewController: UIViewController {
                     self.labelTimerVC.text = format
                 }
                 self.vcTimer.fire()
-                if !QmeraVideoViewController.bSpeakerPhone {
-                    self.setSpeaker()
-                }
             }
         }
     }
@@ -1117,7 +1113,7 @@ class QmeraVideoViewController: UIViewController {
     
     func setSpeaker() {
         DispatchQueue.main.async {
-            QmeraVideoViewController.turnSpeakerOn()
+            QmeraVideoViewController.bSpeakerPhone = !QmeraVideoViewController.bSpeakerPhone
             if (QmeraVideoViewController.bSpeakerPhone) {
                 self.buttonSpeaker.backgroundColor = .lightGray
                 self.buttonSpeaker.tintColor = .mainColor
@@ -1127,6 +1123,9 @@ class QmeraVideoViewController: UIViewController {
                 self.buttonSpeaker.tintColor = .mainColor
                 self.buttonSpeaker.setImage(UIImage(systemName: "speaker.slash", withConfiguration: UIImage.SymbolConfiguration(pointSize: 30, weight: .medium, scale: .default)), for: .normal)
             }
+            DispatchQueue.global().async {
+                QmeraVideoViewController.turnSpeakerOn()
+            }
         }
     }
     
@@ -1289,11 +1288,9 @@ class QmeraVideoViewController: UIViewController {
 //                    } while (QmeraVideoViewController.isLoop)
 //                }
 //            })
+            self.setSpeaker()
         }
         else if (state == Nexilis.VIDEO_CALL_OFFHOOK) {
-            DispatchQueue.main.async {
-                Nexilis.stopRingbacktoneCall()
-            }
             let channel = arrayMessage[3]
             remoteChannel[String(channel)] = String(arrayMessage[5])
             DispatchQueue.main.async {
@@ -1425,6 +1422,12 @@ class QmeraVideoViewController: UIViewController {
             DispatchQueue.main.async {
                 if self.name.isDescendant(of: self.view) {
                     self.didTapAcceptCallButton()
+                    if QmeraVideoViewController.bSpeakerPhone {
+                        DispatchQueue.main.async {
+                            QmeraVideoViewController.bSpeakerPhone = false
+                            self.setSpeaker()
+                        }
+                    }
                 }
                 let indexPerson = self.dataPerson.firstIndex(where: {$0["f_pin"]!! == arrayMessage[1]})
                 if indexPerson != nil {

+ 13 - 3
NexilisLite/NexilisLite/Source/View/Chat/ChatWALikeVC.swift

@@ -324,6 +324,12 @@ public class ChatWALikeVC: UIViewController, UITableViewDataSource, UITableViewD
             }
             return 130.0
         }
+        let fontSize = Int(SecureUserDefaults.shared.value(forKey: "font_size") ?? "0")
+        if fontSize == 4 {
+            return 85.0
+        } else if fontSize == 6 {
+            return 95.0
+        }
         return 75.0
     }
     
@@ -662,8 +668,7 @@ public class ChatWALikeVC: UIViewController, UITableViewDataSource, UITableViewD
         content.addSubview(imageView)
         imageView.translatesAutoresizingMaskIntoConstraints = false
         NSLayoutConstraint.activate([
-            imageView.topAnchor.constraint(equalTo: content.topAnchor, constant: 10.0),
-            imageView.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -10.0),
+            imageView.centerYAnchor.constraint(equalTo: content.centerYAnchor),
             imageView.widthAnchor.constraint(equalToConstant: 55.0),
             imageView.heightAnchor.constraint(equalToConstant: 55.0)
         ])
@@ -969,7 +974,12 @@ public class ChatWALikeVC: UIViewController, UITableViewDataSource, UITableViewD
                 stringURl = "https://" + stringURl.replacingOccurrences(of: "www.", with: "")
             }
             guard let url = URL(string: stringURl) else { return }
-            UIApplication.shared.open(url)
+            if Nexilis.checkingAccess(key: "secure_browser") {
+                APIS.openUrl(url: stringURl)
+            } else {
+                guard let url = URL(string: stringURl) else { return }
+                UIApplication.shared.open(url)
+            }
         } else if selectedTag == AUDIOS_TAG {
             let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
             let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask

+ 108 - 36
NexilisLite/NexilisLite/Source/View/Chat/EditorGroup.swift

@@ -3124,23 +3124,41 @@ extension EditorGroup: UITextViewDelegate, CustomTextViewPasteDelegate {
         return true
     }
     
-    public func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
-            switch interaction {
-            case .invokeDefaultAction:
-                let gesture = ObjectGesture()
-                gesture.message_id = URL.absoluteString
-                tapMessageText(gesture)
-                return false
-            case .presentActions:
-                UIPasteboard.general.string = URL.absoluteString
-                self.view.makeToast("Link Copied".localized(), duration: 3)
-                return false
-            case .preview:
-                return true
-            @unknown default:
-                return true
+    public func textView(_ textView: UITextView, shouldInteractWith URL: URL?, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
+        var urlString: String?
+
+        if let url = URL {
+            urlString = url.absoluteString
+        } else {
+            if let range = Range(characterRange, in: textView.text) {
+                let tappedText = String(textView.text[range])
+                urlString = tappedText
             }
         }
+        
+        guard let finalURL = urlString else {
+            return false
+        }
+
+        switch interaction {
+        case .invokeDefaultAction:
+            let gesture = ObjectGesture()
+            gesture.message_id = finalURL
+            tapMessageText(gesture)
+            return false
+
+        case .presentActions:
+            UIPasteboard.general.string = finalURL
+            self.view.makeToast("Link Copied".localized(), duration: 3)
+            return false
+
+        case .preview:
+            return true
+
+        @unknown default:
+            return true
+        }
+    }
 }
 
 extension EditorGroup: UIContextMenuInteractionDelegate {
@@ -4491,7 +4509,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
         let messageText = UITextView()
         messageText.isEditable = false
         messageText.isSelectable = true
-        messageText.dataDetectorTypes = []
+        messageText.dataDetectorTypes = [.link]
         messageText.backgroundColor = .clear
         messageText.isScrollEnabled = false
         messageText.textContainerInset = UIEdgeInsets.zero
@@ -5105,18 +5123,45 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                     let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
                     if let dirPath = paths.first {
                         let thumbURL = URL(fileURLWithPath: dirPath).appendingPathComponent(listImages[i].thumbId)
-                        let image : UIImage? =  {
-                            if let img = Nexilis.imageCache.object(forKey: listImages[i].thumbId as NSString) {
-                                return img
+                        if FileManager.default.fileExists(atPath: thumbURL.path) {
+                            DispatchQueue.main.async {
+                                let image : UIImage? =  {
+                                    if let img = Nexilis.imageCache.object(forKey: thumbChat as NSString) {
+                                        return img
+                                    }
+                                    else if let img = UIImage(contentsOfFile: thumbURL.path)?.resize(target: CGSize(width: 500, height: 500)) {
+                                            Nexilis.imageCache.setObject(img, forKey: thumbChat as NSString)
+                                            return img
+                                    }
+                                    return nil
+                                }()
+                                imageThumb.image = image
                             }
-                            else if let img = UIImage(contentsOfFile: thumbURL.path)?.resize(target: CGSize(width: 500, height: 500)) {
-                                Nexilis.imageCache.setObject(img, forKey: listImages[i].thumbId as NSString)
-                                return img
+                        } else if FileEncryption.shared.isSecureExists(filename: listImages[i].thumbId) {
+                            do {
+                                if var data = try FileEncryption.shared.readSecure(filename: listImages[i].thumbId) {
+                                    let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
+                                    if dataDecrypt != nil {
+                                        data = dataDecrypt!
+                                    }
+                                    DispatchQueue.main.async {
+                                        let image : UIImage? =  {
+                                            if let img = Nexilis.imageCache.object(forKey: listImages[i].thumbId as NSString) {
+                                                return img
+                                            }
+                                            else if let img = UIImage(data: data)?.resize(target: CGSize(width: 500, height: 500)) {
+                                                Nexilis.imageCache.setObject(img, forKey: listImages[i].thumbId as NSString)
+                                                return img
+                                            }
+                                            return nil
+                                        }()
+                                        imageThumb.image = image
+                                    }
+                                }
+                            } catch {
+                                
                             }
-                            return nil
-                        }()
-                        //                        let image = UIGraphicsRenderer.renderImageAt(url: thumbURL as NSURL, size: CGSize(width: 250, height: 250))
-                        listImageThumb[i].image = image
+                        }
                         
                         let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(listImages[i].imageId)
                         if !FileManager.default.fileExists(atPath: imageURL.path) && !FileEncryption.shared.isSecureExists(filename: imageURL.lastPathComponent) {
@@ -5225,18 +5270,45 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                 let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
                 if let dirPath = paths.first {
                     let thumbURL = URL(fileURLWithPath: dirPath).appendingPathComponent(thumbChat)
-                    let image : UIImage? =  {
-                        if let img = Nexilis.imageCache.object(forKey: thumbChat as NSString) {
-                            return img
+                    if FileManager.default.fileExists(atPath: thumbURL.path) {
+                        DispatchQueue.main.async {
+                            let image : UIImage? =  {
+                                if let img = Nexilis.imageCache.object(forKey: thumbChat as NSString) {
+                                    return img
+                                }
+                                else if let img = UIImage(contentsOfFile: thumbURL.path)?.resize(target: CGSize(width: 500, height: 500)) {
+                                        Nexilis.imageCache.setObject(img, forKey: thumbChat as NSString)
+                                        return img
+                                }
+                                return nil
+                            }()
+                            imageThumb.image = image
                         }
-                        else if let img = UIImage(contentsOfFile: thumbURL.path)?.resize(target: CGSize(width: 500, height: 500)) {
-                            Nexilis.imageCache.setObject(img, forKey: thumbChat as NSString)
-                            return img
+                    } else if FileEncryption.shared.isSecureExists(filename: thumbChat) {
+                        do {
+                            if var data = try FileEncryption.shared.readSecure(filename: thumbChat) {
+                                let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
+                                if dataDecrypt != nil {
+                                    data = dataDecrypt!
+                                }
+                                DispatchQueue.main.async {
+                                    let image : UIImage? =  {
+                                        if let img = Nexilis.imageCache.object(forKey: thumbChat as NSString) {
+                                            return img
+                                        }
+                                        else if let img = UIImage(data: data)?.resize(target: CGSize(width: 500, height: 500)) {
+                                            Nexilis.imageCache.setObject(img, forKey: thumbChat as NSString)
+                                            return img
+                                        }
+                                        return nil
+                                    }()
+                                    imageThumb.image = image
+                                }
+                            }
+                        } catch {
+                            
                         }
-                        return nil
-                    }()
-                    //                let image = UIGraphicsRenderer.renderImageAt(url: thumbURL as NSURL, size: CGSize(width: 250, height: 250))
-                    imageThumb.image = image
+                    }
                     
                     let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(imageChat)
                     if !FileManager.default.fileExists(atPath: imageURL.path) && !FileEncryption.shared.isSecureExists(filename: imageURL.lastPathComponent) {

+ 71 - 27
NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift

@@ -4258,23 +4258,41 @@ extension EditorPersonal: UITextViewDelegate, CustomTextViewPasteDelegate {
         return rawData == gifSignature
     }
     
-    public func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
-            switch interaction {
-            case .invokeDefaultAction:
-                let gesture = ObjectGesture()
-                gesture.message_id = URL.absoluteString
-                tapMessageText(gesture)
-                return false
-            case .presentActions:
-                UIPasteboard.general.string = URL.absoluteString
-                self.view.makeToast("Link Copied".localized(), duration: 3)
-                return false
-            case .preview:
-                return true
-            @unknown default:
-                return true
+    public func textView(_ textView: UITextView, shouldInteractWith URL: URL?, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
+        var urlString: String?
+
+        if let url = URL {
+            urlString = url.absoluteString
+        } else {
+            if let range = Range(characterRange, in: textView.text) {
+                let tappedText = String(textView.text[range])
+                urlString = tappedText
             }
         }
+        
+        guard let finalURL = urlString else {
+            return false
+        }
+
+        switch interaction {
+        case .invokeDefaultAction:
+            let gesture = ObjectGesture()
+            gesture.message_id = finalURL
+            tapMessageText(gesture)
+            return false
+
+        case .presentActions:
+            UIPasteboard.general.string = finalURL
+            self.view.makeToast("Link Copied".localized(), duration: 3)
+            return false
+
+        case .preview:
+            return true
+
+        @unknown default:
+            return true
+        }
+    }
 }
 
 //EUC
@@ -6103,7 +6121,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
         let messageText = UITextView()
         messageText.isEditable = false
         messageText.isSelectable = true
-        messageText.dataDetectorTypes = []
+        messageText.dataDetectorTypes = [.link]
         messageText.backgroundColor = .clear
         messageText.isScrollEnabled = false
         messageText.textContainerInset = UIEdgeInsets.zero
@@ -6584,18 +6602,45 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                     let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
                     if let dirPath = paths.first {
                         let thumbURL = URL(fileURLWithPath: dirPath).appendingPathComponent(listImages[i].thumbId)
-                        let image : UIImage? =  {
-                            if let img = Nexilis.imageCache.object(forKey: listImages[i].thumbId as NSString) {
-                                return img
+                        if FileManager.default.fileExists(atPath: thumbURL.path) {
+                            DispatchQueue.main.async {
+                                let image : UIImage? =  {
+                                    if let img = Nexilis.imageCache.object(forKey: thumbChat as NSString) {
+                                        return img
+                                    }
+                                    else if let img = UIImage(contentsOfFile: thumbURL.path)?.resize(target: CGSize(width: 500, height: 500)) {
+                                            Nexilis.imageCache.setObject(img, forKey: thumbChat as NSString)
+                                            return img
+                                    }
+                                    return nil
+                                }()
+                                imageThumb.image = image
                             }
-                            else if let img = UIImage(contentsOfFile: thumbURL.path)?.resize(target: CGSize(width: 500, height: 500)) {
-                                Nexilis.imageCache.setObject(img, forKey: listImages[i].thumbId as NSString)
-                                return img
+                        } else if FileEncryption.shared.isSecureExists(filename: listImages[i].thumbId) {
+                            do {
+                                if var data = try FileEncryption.shared.readSecure(filename: listImages[i].thumbId) {
+                                    let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
+                                    if dataDecrypt != nil {
+                                        data = dataDecrypt!
+                                    }
+                                    DispatchQueue.main.async {
+                                        let image : UIImage? =  {
+                                            if let img = Nexilis.imageCache.object(forKey: listImages[i].thumbId as NSString) {
+                                                return img
+                                            }
+                                            else if let img = UIImage(data: data)?.resize(target: CGSize(width: 500, height: 500)) {
+                                                Nexilis.imageCache.setObject(img, forKey: listImages[i].thumbId as NSString)
+                                                return img
+                                            }
+                                            return nil
+                                        }()
+                                        imageThumb.image = image
+                                    }
+                                }
+                            } catch {
+                                
                             }
-                            return nil
-                        }()
-//                        let image = UIGraphicsRenderer.renderImageAt(url: thumbURL as NSURL, size: CGSize(width: 250, height: 250))
-                        listImageThumb[i].image = image
+                        }
 
                         let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(listImages[i].imageId)
                         if !FileManager.default.fileExists(atPath: imageURL.path) && !FileEncryption.shared.isSecureExists(filename: imageURL.lastPathComponent) {
@@ -6743,7 +6788,6 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                             
                         }
                     }
-//                    let image = UIGraphicsRenderer.renderImageAt(url: thumbURL as NSURL, size: CGSize(width: 250, height: 250))
                     let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(imageChat)
                     if !FileManager.default.fileExists(atPath: imageURL.path) && !FileEncryption.shared.isSecureExists(filename: imageURL.lastPathComponent) {
                         let blurEffect = UIBlurEffect(style: UIBlurEffect.Style.light)

+ 7 - 2
NexilisLite/NexilisLite/Source/View/Control/ContactChatViewController.swift

@@ -1174,8 +1174,7 @@ extension ContactChatViewController {
                 content.addSubview(imageView)
                 imageView.translatesAutoresizingMaskIntoConstraints = false
                 NSLayoutConstraint.activate([
-                    imageView.topAnchor.constraint(equalTo: content.topAnchor, constant: 10.0),
-                    imageView.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -10.0),
+                    imageView.centerYAnchor.constraint(equalTo: content.centerYAnchor),
                     imageView.widthAnchor.constraint(equalToConstant: 55.0),
                     imageView.heightAnchor.constraint(equalToConstant: 55.0)
                 ])
@@ -1642,6 +1641,12 @@ extension ContactChatViewController {
     }
     
     override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
+        let fontSize = Int(SecureUserDefaults.shared.value(forKey: "font_size") ?? "0")
+        if fontSize == 4 {
+            return 85.0
+        } else if fontSize == 6 {
+            return 95.0
+        }
         return 75.0
     }
     

+ 3 - 1
NexilisLite/NexilisLite/Source/View/Control/QRScannerController.swift

@@ -59,7 +59,9 @@ class QRScannerController: UIViewController, AVCaptureMetadataOutputObjectsDeleg
         view.layer.addSublayer(previewLayer)
 
         // Start the capture session
-        captureSession.startRunning()
+        DispatchQueue.main.async {
+            self.captureSession.startRunning()
+        }
     }
 
     override func viewWillDisappear(_ animated: Bool) {

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

@@ -654,6 +654,7 @@ public class SettingTableViewController: UITableViewController, UIGestureRecogni
 //                            Nexilis.changeUser(f_pin: id)
                             SecureUserDefaults.shared.set(id, forKey: "me")
                             APIS.sendPushToken(Utils.getTokenAPN(), isResend: true)
+                            Nexilis.sendVersionToBE()
                             Utils.setProfile(value: false)
                             if Utils.getForceAnonymous() {
                                 self.deleteAllRecordDatabase()