Jelajahi Sumber

update sound Message and Certificate Pinning Webview

alqindiirsyam 6 bulan lalu
induk
melakukan
6f30c32a1e
38 mengubah file dengan 738 tambahan dan 576 penghapusan
  1. TEMPAT SAMPAH
      AppBuilder/.DS_Store
  2. 24 24
      AppBuilder/AppBuilder.xcodeproj/project.pbxproj
  3. 1 1
      AppBuilder/AppBuilder/AppDelegate.swift
  4. 122 81
      AppBuilder/AppBuilder/FirstTabViewController.swift
  5. 105 57
      AppBuilder/AppBuilder/ThirdTabViewController.swift
  6. 0 28
      AppBuilder/Podfile
  7. TEMPAT SAMPAH
      NexilisLite/.DS_Store
  8. 2 2
      NexilisLite/NexilisLite.podspec
  9. 56 127
      NexilisLite/NexilisLite.xcodeproj/project.pbxproj
  10. TEMPAT SAMPAH
      NexilisLite/NexilisLite/.DS_Store
  11. TEMPAT SAMPAH
      NexilisLite/NexilisLite/Resource/.DS_Store
  12. TEMPAT SAMPAH
      NexilisLite/NexilisLite/Resource/Sound/Dutifully.mp3
  13. TEMPAT SAMPAH
      NexilisLite/NexilisLite/Resource/Sound/Elegant.mp3
  14. TEMPAT SAMPAH
      NexilisLite/NexilisLite/Resource/Sound/Eventually.mp3
  15. TEMPAT SAMPAH
      NexilisLite/NexilisLite/Resource/Sound/Hangover.mp3
  16. TEMPAT SAMPAH
      NexilisLite/NexilisLite/Resource/Sound/Juntos.mp3
  17. TEMPAT SAMPAH
      NexilisLite/NexilisLite/Resource/Sound/Light_Hearted.mp3
  18. TEMPAT SAMPAH
      NexilisLite/NexilisLite/Resource/Sound/Magic.mp3
  19. TEMPAT SAMPAH
      NexilisLite/NexilisLite/Resource/Sound/Nexilis_Message.mp3
  20. TEMPAT SAMPAH
      NexilisLite/NexilisLite/Resource/Sound/Out_of_Nowhere.mp3
  21. TEMPAT SAMPAH
      NexilisLite/NexilisLite/Resource/Sound/Relax.mp3
  22. TEMPAT SAMPAH
      NexilisLite/NexilisLite/Resource/Sound/Strong_Minded.mp3
  23. TEMPAT SAMPAH
      NexilisLite/NexilisLite/Resource/Sound/Swift_gesture.mp3
  24. TEMPAT SAMPAH
      NexilisLite/NexilisLite/Resource/Sound/Upset.mp3
  25. 2 0
      NexilisLite/NexilisLite/Resource/id.lproj/Localizable.strings
  26. 20 17
      NexilisLite/NexilisLite/Source/APIS.swift
  27. 46 4
      NexilisLite/NexilisLite/Source/Callback.swift
  28. 3 7
      NexilisLite/NexilisLite/Source/IncomingThread.swift
  29. 2 2
      NexilisLite/NexilisLite/Source/Model/Chat.swift
  30. 24 19
      NexilisLite/NexilisLite/Source/Nexilis.swift
  31. 4 6
      NexilisLite/NexilisLite/Source/OutgoingThread.swift
  32. 3 3
      NexilisLite/NexilisLite/Source/Utils.swift
  33. 104 40
      NexilisLite/NexilisLite/Source/View/BNIView/BNIBookingWebView.swift
  34. 34 28
      NexilisLite/NexilisLite/Source/View/Chat/EditorGroup.swift
  35. 31 25
      NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift
  36. 108 89
      NexilisLite/NexilisLite/Source/View/Chat/PreviewAttachmentImageVideo.swift
  37. 46 15
      NexilisLite/NexilisLite/Source/View/Control/NotificationSound.swift
  38. 1 1
      NexilisLite/Podfile

TEMPAT SAMPAH
AppBuilder/.DS_Store


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

@@ -14,7 +14,7 @@
 		2401CEA1275490DB00B323BB /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2401CE9F275490DB00B323BB /* Main.storyboard */; };
 		2401CEA3275490E600B323BB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2401CEA2275490E600B323BB /* Assets.xcassets */; };
 		2401CEA6275490E600B323BB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2401CEA4275490E600B323BB /* LaunchScreen.storyboard */; };
-		5E96FB76884562D8D9229EDB /* Pods_AppBuilder.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 187C913CD69AB39558A1C591 /* Pods_AppBuilder.framework */; };
+		58945074FC459F87D8C4EDA4 /* Pods_AppBuilder.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D246950EF11B497A8C2CE4B7 /* Pods_AppBuilder.framework */; };
 		A413B18727EACB20006D16EB /* PrefsUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = A413B18627EACB20006D16EB /* PrefsUtil.swift */; };
 		A42ED92227F30BA200B0FAB7 /* FirstTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A42ED92127F30BA200B0FAB7 /* FirstTabViewController.swift */; };
 		A42ED92427F3FC2F00B0FAB7 /* SecondTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A42ED92327F3FC2F00B0FAB7 /* SecondTabViewController.swift */; };
@@ -56,9 +56,8 @@
 /* End PBXCopyFilesBuildPhase section */
 
 /* Begin PBXFileReference section */
-		08F5DF9A7B8CA1560B8878A0 /* Pods-AppBuilder.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppBuilder.release.xcconfig"; path = "Target Support Files/Pods-AppBuilder/Pods-AppBuilder.release.xcconfig"; sourceTree = "<group>"; };
+		005671E57652801092CB2F74 /* Pods-AppBuilder.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppBuilder.debug.xcconfig"; path = "Target Support Files/Pods-AppBuilder/Pods-AppBuilder.debug.xcconfig"; sourceTree = "<group>"; };
 		12960ADF2892361000A467DD /* FourthTabViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FourthTabViewController.swift; sourceTree = "<group>"; };
-		187C913CD69AB39558A1C591 /* Pods_AppBuilder.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AppBuilder.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		2401CE96275490DB00B323BB /* AppBuilder.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AppBuilder.app; sourceTree = BUILT_PRODUCTS_DIR; };
 		2401CE99275490DB00B323BB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
 		2401CE9B275490DB00B323BB /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
@@ -67,7 +66,7 @@
 		2401CEA2275490E600B323BB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
 		2401CEA5275490E600B323BB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
 		2401CEA7275490E600B323BB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
-		7C79883C9EF677263426AD2E /* Pods-AppBuilder.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppBuilder.debug.xcconfig"; path = "Target Support Files/Pods-AppBuilder/Pods-AppBuilder.debug.xcconfig"; sourceTree = "<group>"; };
+		77BA1C01F16978F06C6E2031 /* Pods-AppBuilder.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppBuilder.release.xcconfig"; path = "Target Support Files/Pods-AppBuilder/Pods-AppBuilder.release.xcconfig"; sourceTree = "<group>"; };
 		A413B18627EACB20006D16EB /* PrefsUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsUtil.swift; sourceTree = "<group>"; };
 		A42ED92127F30BA200B0FAB7 /* FirstTabViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstTabViewController.swift; sourceTree = "<group>"; };
 		A42ED92327F3FC2F00B0FAB7 /* SecondTabViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondTabViewController.swift; sourceTree = "<group>"; };
@@ -84,6 +83,7 @@
 		CD9D59D72BEE1D30008014B4 /* kmi_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = kmi_icon.png; sourceTree = "<group>"; };
 		CD9D59D82BEE1D30008014B4 /* nu_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = nu_icon.png; sourceTree = "<group>"; };
 		CDE27BA42D53641D006298BD /* AppBuilder.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AppBuilder.entitlements; sourceTree = "<group>"; };
+		D246950EF11B497A8C2CE4B7 /* Pods_AppBuilder.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AppBuilder.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -91,28 +91,20 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				5E96FB76884562D8D9229EDB /* Pods_AppBuilder.framework in Frameworks */,
+				58945074FC459F87D8C4EDA4 /* Pods_AppBuilder.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
-		2203E7E4447BFC430EE46F72 /* Frameworks */ = {
-			isa = PBXGroup;
-			children = (
-				187C913CD69AB39558A1C591 /* Pods_AppBuilder.framework */,
-			);
-			name = Frameworks;
-			sourceTree = "<group>";
-		};
 		2401CE8D275490DB00B323BB = {
 			isa = PBXGroup;
 			children = (
 				2401CE98275490DB00B323BB /* AppBuilder */,
+				76BBCA5783FCBB787B29FC82 /* Frameworks */,
 				6E32BCCF4DE50EE1A90E8AAE /* Pods */,
 				2401CE97275490DB00B323BB /* Products */,
-				2203E7E4447BFC430EE46F72 /* Frameworks */,
 			);
 			sourceTree = "<group>";
 		};
@@ -158,12 +150,20 @@
 		6E32BCCF4DE50EE1A90E8AAE /* Pods */ = {
 			isa = PBXGroup;
 			children = (
-				7C79883C9EF677263426AD2E /* Pods-AppBuilder.debug.xcconfig */,
-				08F5DF9A7B8CA1560B8878A0 /* Pods-AppBuilder.release.xcconfig */,
+				005671E57652801092CB2F74 /* Pods-AppBuilder.debug.xcconfig */,
+				77BA1C01F16978F06C6E2031 /* Pods-AppBuilder.release.xcconfig */,
 			);
 			path = Pods;
 			sourceTree = "<group>";
 		};
+		76BBCA5783FCBB787B29FC82 /* Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+				D246950EF11B497A8C2CE4B7 /* Pods_AppBuilder.framework */,
+			);
+			name = Frameworks;
+			sourceTree = "<group>";
+		};
 /* End PBXGroup section */
 
 /* Begin PBXNativeTarget section */
@@ -171,13 +171,13 @@
 			isa = PBXNativeTarget;
 			buildConfigurationList = 2401CEC0275490E600B323BB /* Build configuration list for PBXNativeTarget "AppBuilder" */;
 			buildPhases = (
-				BCAB5948E2E15FB18EE7AF7D /* [CP] Check Pods Manifest.lock */,
+				7B7ED6AEAA0D03CB68EFC23A /* [CP] Check Pods Manifest.lock */,
 				2401CE92275490DB00B323BB /* Sources */,
 				2401CE93275490DB00B323BB /* Frameworks */,
 				2401CE94275490DB00B323BB /* Resources */,
 				247E0A722796969200430E5F /* Embed Frameworks */,
 				CDEE3DD129B06E1E00B420E5 /* Embed Foundation Extensions */,
-				2C6EDAE79F7A5261B33B05F7 /* [CP] Embed Pods Frameworks */,
+				6237F8E2B28CDC232617A789 /* [CP] Embed Pods Frameworks */,
 			);
 			buildRules = (
 			);
@@ -245,7 +245,7 @@
 /* End PBXResourcesBuildPhase section */
 
 /* Begin PBXShellScriptBuildPhase section */
-		2C6EDAE79F7A5261B33B05F7 /* [CP] Embed Pods Frameworks */ = {
+		6237F8E2B28CDC232617A789 /* [CP] Embed Pods Frameworks */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
@@ -262,7 +262,7 @@
 			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AppBuilder/Pods-AppBuilder-frameworks.sh\"\n";
 			showEnvVarsInLog = 0;
 		};
-		BCAB5948E2E15FB18EE7AF7D /* [CP] Check Pods Manifest.lock */ = {
+		7B7ED6AEAA0D03CB68EFC23A /* [CP] Check Pods Manifest.lock */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
@@ -442,7 +442,7 @@
 		};
 		2401CEC1275490E600B323BB /* Debug */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 7C79883C9EF677263426AD2E /* Pods-AppBuilder.debug.xcconfig */;
+			baseConfigurationReference = 005671E57652801092CB2F74 /* Pods-AppBuilder.debug.xcconfig */;
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
@@ -463,7 +463,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 5.0.3g;
+				MARKETING_VERSION = 5.0.4;
 				PRODUCT_BUNDLE_IDENTIFIER = io.newuniverse.AppBuilder;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
@@ -478,7 +478,7 @@
 		};
 		2401CEC2275490E600B323BB /* Release */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 08F5DF9A7B8CA1560B8878A0 /* Pods-AppBuilder.release.xcconfig */;
+			baseConfigurationReference = 77BA1C01F16978F06C6E2031 /* Pods-AppBuilder.release.xcconfig */;
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
@@ -499,7 +499,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 5.0.3g;
+				MARKETING_VERSION = 5.0.4;
 				PRODUCT_BUNDLE_IDENTIFIER = io.newuniverse.AppBuilder;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";

+ 1 - 1
AppBuilder/AppBuilder/AppDelegate.swift

@@ -82,7 +82,7 @@ extension AppDelegate: ConnectDelegate, UNUserNotificationCenterDelegate {
         completionHandler(.newData)
     }
     func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
-        completionHandler([.alert, .sound, .badge])
+        completionHandler([.banner, .sound, .badge])
     }
     
     func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {

+ 122 - 81
AppBuilder/AppBuilder/FirstTabViewController.swift

@@ -32,6 +32,8 @@ class FirstTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
     
     var indexImageVideoWv = 0
     var imageVideoPicker: ImageVideoPicker!
+    var blockedCertificate = ""
+    var allowedURLs = Set<String>()
     
     override func viewDidLoad() {
         super.viewDidLoad()
@@ -171,37 +173,31 @@ class FirstTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
     }
     
     override func viewDidAppear(_ animated: Bool) {
-//        if (self.isUsingMyWebview() && self.webView.url != nil && !self.webView.url!.absoluteString.contains("nexilis/pages/tab1-main-only") && !self.webView.url!.absoluteString.contains("nexilis/pages/tab3-main-only") && !self.webView.url!.absoluteString.contains("nexilis/pages/tab1-main") && !self.webView.url!.absoluteString.contains("nexilis/pages/tab3-commerce") && !self.webView.url!.absoluteString.contains("nexilis/pages/tab1-video") && !self.webView.url!.absoluteString.contains("nexilis/pages/tab3-main")) || FirstTabViewController.showModal {
-//            ViewController.alwaysHideButton = true
-//            self.hideTabBar()
-//            ThirdTabViewController.atFirstPage = false
-//        } else {
-            DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: {
-                var viewController = UIApplication.shared.windows.first!.rootViewController
-                if !(viewController is ViewController) {
-                    viewController = self.parent
+        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: {
+            var viewController = UIApplication.shared.windows.first!.rootViewController
+            if !(viewController is ViewController) {
+                viewController = self.parent
+            }
+            if ViewController.middleButton.isHidden {
+                ViewController.isExpandButton = false
+                if let viewController = viewController as? ViewController {
+                    if viewController.tabBar.isHidden {
+                        viewController.tabBar.isHidden = false
+                        ViewController.middleButton.isHidden = false
+                        ViewController.alwaysHideButton = false
+                    }
                 }
-                if ViewController.middleButton.isHidden {
-                    ViewController.isExpandButton = false
+            } else if PrefsUtil.getCpaasMode() != PrefsUtil.CPAAS_MODE_DOCKED {
+                DispatchQueue.main.async {
                     if let viewController = viewController as? ViewController {
                         if viewController.tabBar.isHidden {
                             viewController.tabBar.isHidden = false
-                            ViewController.middleButton.isHidden = false
                             ViewController.alwaysHideButton = false
                         }
                     }
-                } else if PrefsUtil.getCpaasMode() != PrefsUtil.CPAAS_MODE_DOCKED {
-                    DispatchQueue.main.async {
-                        if let viewController = viewController as? ViewController {
-                            if viewController.tabBar.isHidden {
-                                viewController.tabBar.isHidden = false
-                                ViewController.alwaysHideButton = false
-                            }
-                        }
-                    }
                 }
-            })
-//        }
+            }
+        })
     }
     
     @objc func onShowAC(notification: NSNotification) {
@@ -241,6 +237,8 @@ class FirstTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
 
     @objc func reloadWebView(_ sender: UIRefreshControl) {
         webView.reload()
+        ViewController.alwaysHideButton = false
+        showTabBar()
         sender.endRefreshing()
     }
     
@@ -688,72 +686,115 @@ extension FirstTabViewController: SFSpeechRecognizerDelegate {
 }
 
 extension FirstTabViewController: WKUIDelegate, WKNavigationDelegate {
-    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
-        if self.viewIfLoaded?.window != nil {
-          if let urlStr = navigationAction.request.url?.absoluteString {
-              //print("url: \(urlStr)")
-              collapseDocked()
-              if urlStr.contains("nexilis/pages/tab1-main-only") || urlStr.contains("nexilis/pages/tab3-main-only") || urlStr.contains("nexilis/pages/tab1-main") || urlStr.contains("nexilis/pages/tab3-commerce") || urlStr.contains("nexilis/pages/tab1-video") || urlStr.contains("nexilis/pages/tab3-main") {
-                  ViewController.alwaysHideButton = false
-                  showTabBar()
-                  FirstTabViewController.atFirstPage = true
-              }
-    //                  else if isUsingMyWebview() {
-    //                      ViewController.alwaysHideButton = true
-    //                      hideTabBar()
-    //                      FirstTabViewController.atFirstPage = false
-    //                  }
-          }
-        }
-        decisionHandler(.allow)
+    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping @MainActor (WKNavigationActionPolicy) -> Void) {
+        guard let url = navigationAction.request.url else {
+            decisionHandler(.cancel)
+            return
+        }
+        if allowedURLs.contains(url.absoluteString) {
+            print("✅ URL already allowed: \(url)")
+            decisionHandler(.allow)
+            return
+        }
+        validateSSLCertificate(url: url) { isValid in
+            print("is VALID? : \(isValid)")
+            if isValid {
+                self.allowedURLs.insert(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)
+                            }
+                        }
+                        self.allowedURLs.insert(url.absoluteString)
+                        decisionHandler(.allow)
+                    }
+                    let noAction = UIAlertAction(title: "No", style: .cancel) { _ in
+                        decisionHandler(.cancel)
+                    }
+                    alert.addAction(yesAction)
+                    alert.addAction(noAction)
+                    self.present(alert, animated: true, completion: nil)
+                }
+            }
+        }
     }
-//    func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
-//        guard let serverTrust = challenge.protectionSpace.serverTrust else {
-//            completionHandler(.cancelAuthenticationChallenge, nil)
-//            return
-//        }
-//        if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0),
-//           let pinnedCertificateHash = getCertificateHash(from: serverCertificate),
-//           pinnedCertificateHash == Utils.getCertificatePinningWebview() {
-//            let credential = URLCredential(trust: serverTrust)
-//            completionHandler(.useCredential, credential) // Certificate matches, proceed
-//        } else {
-//            completionHandler(.cancelAuthenticationChallenge, nil) // Certificate doesn't match, cancel
-//        }
-//    }
     
-    private func getCertificateHash(from certificate: SecCertificate) -> String? {
-        guard let publicKey = getPublicKey(from: certificate) else { return nil }
-        return hashPublicKey(publicKey)
+    private func validateSSLCertificate(url: URL, completion: @escaping (Bool) -> Void) {
+        let session = URLSession(configuration: .ephemeral, delegate: self, delegateQueue: nil)
+        let request = URLRequest(url: url)
+
+        let task = session.dataTask(with: request) { _, response, error in
+            if let error = error {
+                print("SSL Validation Error: \(error.localizedDescription)")
+                completion(false)
+                return
+            }
+            completion(true)
+        }
+        task.resume()
     }
-    
-    private func getPublicKey(from certificate: SecCertificate) -> Data? {
-        var trust: SecTrust?
-        let policy = SecPolicyCreateBasicX509()
-        let status = SecTrustCreateWithCertificates(certificate, policy, &trust)
-
-        guard status == errSecSuccess, let trust = trust else { return nil }
-
-        guard let publicKey = SecTrustCopyKey(trust) else { return nil }
+}
 
-        var error: Unmanaged<CFError>?
-        if let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, &error) {
-            return publicKeyData as Data
+extension FirstTabViewController: URLSessionDelegate {
+    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
+        guard let serverTrust = challenge.protectionSpace.serverTrust else {
+            completionHandler(.cancelAuthenticationChallenge, nil)
+            return
+        }
+        if let publicKeyHash = extractPublicKeyHash(from: serverTrust) {
+            let domain = challenge.protectionSpace.host
+            let storedCertificate = Utils.getCertificatePinningWebview()
+            if let jsonData = storedCertificate.data(using: .utf8),
+               let certJson = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: String] {
+                if publicKeyHash == certJson[domain] {
+                    print("✅ Certificate Matched. Allowing Navigation.")
+                    completionHandler(.useCredential, URLCredential(trust: serverTrust))
+                } else {
+                    print("❌ Certificate Mismatch! Blocking Navigation.")
+                    blockedCertificate = publicKeyHash
+                    completionHandler(.cancelAuthenticationChallenge, nil)
+                }
+            }
         } else {
-            print("Error extracting public key: \(String(describing: error?.takeRetainedValue()))")
-            return nil
+            completionHandler(.cancelAuthenticationChallenge, nil)
         }
     }
-
-    private func hashPublicKey(_ publicKey: Data) -> String? {
-        // SHA-256 hash
+    
+    func extractPublicKeyHash(from serverTrust: SecTrust) -> String? {
+        guard let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else { return nil }
+        guard let publicKey = SecCertificateCopyKey(certificate) else { return nil }
+        
+        var error: Unmanaged<CFError>?
+        guard let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, &error) as Data? else {
+            print("❌ Failed to extract public key")
+            return nil
+        }
+        
+        // Compute SHA-256 hash
         var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
-        publicKey.withUnsafeBytes {
-            _ = CC_SHA256($0.baseAddress, CC_LONG(publicKey.count), &hash)
+        publicKeyData.withUnsafeBytes {
+            _ = CC_SHA256($0.baseAddress, CC_LONG(publicKeyData.count), &hash)
         }
-
-        // Base64 encode the hash
-        let base64Hash = Data(hash).base64EncodedString()
-        return "sha256/\(base64Hash)"
+        
+        let hashData = Data(hash)
+        let base64Hash = hashData.base64EncodedString()
+        
+        return base64Hash
     }
 }

+ 105 - 57
AppBuilder/AppBuilder/ThirdTabViewController.swift

@@ -33,6 +33,8 @@ class ThirdTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
     
     var indexImageVideoWv = 0
     var imageVideoPicker: ImageVideoPicker!
+    var blockedCertificate = ""
+    var allowedURLs = Set<String>()
     
     override func viewDidLoad() {
         super.viewDidLoad()
@@ -254,6 +256,8 @@ class ThirdTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
     
     @objc func reloadWebView(_ sender: UIRefreshControl) {
         webView.reload()
+        ViewController.alwaysHideButton = false
+        showTabBar()
         sender.endRefreshing()
     }
 
@@ -702,71 +706,115 @@ extension ThirdTabViewController: SFSpeechRecognizerDelegate {
 }
 
 extension ThirdTabViewController: WKUIDelegate {
-    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
-        if self.viewIfLoaded?.window != nil {
-          if let urlStr = navigationAction.request.url?.absoluteString {
-              collapseDocked()
-              if urlStr.contains("nexilis/pages/tab1-main-only") || urlStr.contains("nexilis/pages/tab3-main-only") || urlStr.contains("nexilis/pages/tab1-main") || urlStr.contains("nexilis/pages/tab3-commerce") || urlStr.contains("nexilis/pages/tab1-video") || urlStr.contains("nexilis/pages/tab3-main") {
-                  ViewController.alwaysHideButton = false
-                  showTabBar()
-                  ThirdTabViewController.atFirstPage = true
-              }
-    //                  else if isUsingMyWebview() {
-    //                      ViewController.alwaysHideButton = true
-    //                      hideTabBar()
-    //                      ThirdTabViewController.atFirstPage = false
-    //                  }
-          }
-        }
-        decisionHandler(.allow)
+    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping @MainActor (WKNavigationActionPolicy) -> Void) {
+        guard let url = navigationAction.request.url else {
+            decisionHandler(.cancel)
+            return
+        }
+        if allowedURLs.contains(url.absoluteString) {
+            print("✅ URL already allowed: \(url)")
+            decisionHandler(.allow)
+            return
+        }
+        validateSSLCertificate(url: url) { isValid in
+            print("is VALID? : \(isValid)")
+            if isValid {
+                self.allowedURLs.insert(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)
+                            }
+                        }
+                        self.allowedURLs.insert(url.absoluteString)
+                        decisionHandler(.allow)
+                    }
+                    let noAction = UIAlertAction(title: "No", style: .cancel) { _ in
+                        decisionHandler(.cancel)
+                    }
+                    alert.addAction(yesAction)
+                    alert.addAction(noAction)
+                    self.present(alert, animated: true, completion: nil)
+                }
+            }
+        }
     }
-//    func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
-//        guard let serverTrust = challenge.protectionSpace.serverTrust else {
-//            completionHandler(.cancelAuthenticationChallenge, nil)
-//            return
-//        }
-//        if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0),
-//           let pinnedCertificateHash = getCertificateHash(from: serverCertificate),
-//           pinnedCertificateHash == Utils.getCertificatePinningWebview() {
-//            let credential = URLCredential(trust: serverTrust)
-//            completionHandler(.useCredential, credential) // Certificate matches, proceed
-//        } else {
-//            completionHandler(.cancelAuthenticationChallenge, nil) // Certificate doesn't match, cancel
-//        }
-//    }
     
-    private func getCertificateHash(from certificate: SecCertificate) -> String? {
-        guard let publicKey = getPublicKey(from: certificate) else { return nil }
-        return hashPublicKey(publicKey)
+    private func validateSSLCertificate(url: URL, completion: @escaping (Bool) -> Void) {
+        let session = URLSession(configuration: .ephemeral, delegate: self, delegateQueue: nil)
+        let request = URLRequest(url: url)
+
+        let task = session.dataTask(with: request) { _, response, error in
+            if let error = error {
+                print("SSL Validation Error: \(error.localizedDescription)")
+                completion(false)
+                return
+            }
+            completion(true)
+        }
+        task.resume()
     }
-    
-    private func getPublicKey(from certificate: SecCertificate) -> Data? {
-        var trust: SecTrust?
-        let policy = SecPolicyCreateBasicX509()
-        let status = SecTrustCreateWithCertificates(certificate, policy, &trust)
-
-        guard status == errSecSuccess, let trust = trust else { return nil }
-
-        guard let publicKey = SecTrustCopyKey(trust) else { return nil }
+}
 
-        var error: Unmanaged<CFError>?
-        if let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, &error) {
-            return publicKeyData as Data
+extension ThirdTabViewController: URLSessionDelegate {
+    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
+        guard let serverTrust = challenge.protectionSpace.serverTrust else {
+            completionHandler(.cancelAuthenticationChallenge, nil)
+            return
+        }
+        if let publicKeyHash = extractPublicKeyHash(from: serverTrust) {
+            let domain = challenge.protectionSpace.host
+            let storedCertificate = Utils.getCertificatePinningWebview()
+            if let jsonData = storedCertificate.data(using: .utf8),
+               let certJson = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: String] {
+                if publicKeyHash == certJson[domain] {
+                    print("✅ Certificate Matched. Allowing Navigation.")
+                    completionHandler(.useCredential, URLCredential(trust: serverTrust))
+                } else {
+                    print("❌ Certificate Mismatch! Blocking Navigation.")
+                    blockedCertificate = publicKeyHash
+                    completionHandler(.cancelAuthenticationChallenge, nil)
+                }
+            }
         } else {
-            print("Error extracting public key: \(String(describing: error?.takeRetainedValue()))")
-            return nil
+            completionHandler(.cancelAuthenticationChallenge, nil)
         }
     }
-
-    private func hashPublicKey(_ publicKey: Data) -> String? {
-        // SHA-256 hash
+    
+    func extractPublicKeyHash(from serverTrust: SecTrust) -> String? {
+        guard let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else { return nil }
+        guard let publicKey = SecCertificateCopyKey(certificate) else { return nil }
+        
+        var error: Unmanaged<CFError>?
+        guard let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, &error) as Data? else {
+            print("❌ Failed to extract public key")
+            return nil
+        }
+        
+        // Compute SHA-256 hash
         var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
-        publicKey.withUnsafeBytes {
-            _ = CC_SHA256($0.baseAddress, CC_LONG(publicKey.count), &hash)
+        publicKeyData.withUnsafeBytes {
+            _ = CC_SHA256($0.baseAddress, CC_LONG(publicKeyData.count), &hash)
         }
-
-        // Base64 encode the hash
-        let base64Hash = Data(hash).base64EncodedString()
-        return "sha256/\(base64Hash)"
+        
+        let hashData = Data(hash)
+        let base64Hash = hashData.base64EncodedString()
+        
+        return base64Hash
     }
 }

+ 0 - 28
AppBuilder/Podfile

@@ -2,34 +2,6 @@
 platform :ios, '14.0'
 
 target 'AppBuilder' do
-  # Comment the next line if you don't want to use dynamic frameworks
   use_frameworks!
-
   pod 'NexilisLite', :path => '../NexilisLite'
-#  pod 'nuSDKService', '~> 0.0.8'
-#  pod 'FMDB', '~> 2.7.5'
-#  pod 'NotificationBannerSwift', '~> 3.2.1'
-#  pod 'Alamofire', '~> 5.7.0'
-#  pod 'SDWebImage', '~> 5.15.7'
-#  pod 'Toast-Swift', '~> 5.0.1'
-
-  # Pods for AppBuilder
-
-  # target 'AppBuilderTests' do
-    #inherit! :search_paths
-    # Pods for testing
-  # end
-
-  #target 'AppBuilderUITests' do
-    # Pods for testing
-  #end
-  
-#  post_install do |installer|
-#    installer.pods_project.targets.each do |target|
-#      target.build_configurations.each do |config|
-#        config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'
-#      end
-#    end
-#  end
-
 end

TEMPAT SAMPAH
NexilisLite/.DS_Store


+ 2 - 2
NexilisLite/NexilisLite.podspec

@@ -24,7 +24,7 @@ Pod::Spec.new do |spec|
   spec.resource_bundles = { 'NexilisLite' => ['NexilisLite/Resource/**/*']}
   spec.swift_version = '5.5.1'
   spec.dependency 'FMDB', '~> 2.7.12'
-  spec.dependency 'nuSDKService', '~> 4.0.2'
+  spec.dependency 'nuSDKService', '~> 4.0.3'
   spec.dependency 'NotificationBannerSwift', '~> 3.1.0'
   spec.dependency 'Alamofire', '~> 5.10.1'
   spec.dependency 'SDWebImage', '~> 5.20.0'
@@ -33,7 +33,7 @@ Pod::Spec.new do |spec|
   spec.dependency 'SwiftLinkPreview', '~> 3.4.0'
   spec.dependency 'KeychainAccess'
   spec.dependency 'Popover'
-#  spec.static_framework = true
+# spec.static_framework = true
 # spec.dependency 'iOS-WebP'
 #  spec.vendored_frameworks = 'nuSDKService.framework'
   spec.ios.vendored_frameworks = "NexilisLite.framework"

+ 56 - 127
NexilisLite/NexilisLite.xcodeproj/project.pbxproj

@@ -10,7 +10,6 @@
 		1241AEBC2D017E8C0088175A /* MasterKeyUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1241AEBB2D017E8C0088175A /* MasterKeyUtil.swift */; };
 		1241AEBD2D017E8C0088175A /* FileEncryption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1241AEBA2D017E8C0088175A /* FileEncryption.swift */; };
 		12C36CEB2D0299630095BEC1 /* SecureFolderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12C36CEA2D02995F0095BEC1 /* SecureFolderView.swift */; };
-		A26061F6D200295A7E5689A6 /* Pods_NexilisLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FCEE47B4987D63B825DAFB26 /* Pods_NexilisLite.framework */; };
 		CD1E6E6D2A0B7C3600BF871F /* NexilisLite.docc in Sources */ = {isa = PBXBuildFile; fileRef = CD1E6E6C2A0B7C3600BF871F /* NexilisLite.docc */; };
 		CD1E6E732A0B7C3600BF871F /* NexilisLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD1E6E682A0B7C3600BF871F /* NexilisLite.framework */; };
 		CD1E6E782A0B7C3600BF871F /* NexilisLiteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1E6E772A0B7C3600BF871F /* NexilisLiteTests.swift */; };
@@ -106,6 +105,21 @@
 		CD1E72612A0BA86100BF871F /* CoreMessage_TMessageCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1E71B32A0BA86100BF871F /* CoreMessage_TMessageCode.swift */; };
 		CD1E72622A0BA86100BF871F /* IncomingThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1E71B42A0BA86100BF871F /* IncomingThread.swift */; };
 		CD1E72632A0BA86100BF871F /* CoreMessage_TMessageUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1E71B52A0BA86100BF871F /* CoreMessage_TMessageUtil.swift */; };
+		CD2585C42D59DA60002AB416 /* Dutifully.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = CD2585B72D59DA60002AB416 /* Dutifully.mp3 */; };
+		CD2585C52D59DA60002AB416 /* Juntos.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = CD2585BB2D59DA60002AB416 /* Juntos.mp3 */; };
+		CD2585C62D59DA60002AB416 /* Light_Hearted.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = CD2585BC2D59DA60002AB416 /* Light_Hearted.mp3 */; };
+		CD2585C72D59DA60002AB416 /* Nexilis_Message.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = CD2585BE2D59DA60002AB416 /* Nexilis_Message.mp3 */; };
+		CD2585C82D59DA60002AB416 /* Out_of_Nowhere.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = CD2585BF2D59DA60002AB416 /* Out_of_Nowhere.mp3 */; };
+		CD2585C92D59DA60002AB416 /* Swift_gesture.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = CD2585C22D59DA60002AB416 /* Swift_gesture.mp3 */; };
+		CD2585CA2D59DA60002AB416 /* Relax.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = CD2585C02D59DA60002AB416 /* Relax.mp3 */; };
+		CD2585CB2D59DA60002AB416 /* Eventually.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = CD2585B92D59DA60002AB416 /* Eventually.mp3 */; };
+		CD2585CC2D59DA60002AB416 /* Upset.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = CD2585C32D59DA60002AB416 /* Upset.mp3 */; };
+		CD2585CD2D59DA60002AB416 /* Elegant.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = CD2585B82D59DA60002AB416 /* Elegant.mp3 */; };
+		CD2585CE2D59DA60002AB416 /* Strong_Minded.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = CD2585C12D59DA60002AB416 /* Strong_Minded.mp3 */; };
+		CD2585CF2D59DA60002AB416 /* Magic.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = CD2585BD2D59DA60002AB416 /* Magic.mp3 */; };
+		CD2585D02D59DA60002AB416 /* Hangover.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = CD2585BA2D59DA60002AB416 /* Hangover.mp3 */; };
+		CD2585D12D59DA73002AB416 /* pb_call_in.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = CDE3BED62C86E3A000B0BF36 /* pb_call_in.mp3 */; };
+		CD2585D22D59DA79002AB416 /* pb_call_out.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = CDE3BED72C86E3A000B0BF36 /* pb_call_out.mp3 */; };
 		CD2B01212D43945500C11B20 /* pb_gpt_bot.gif in Resources */ = {isa = PBXBuildFile; fileRef = CD2B01202D43945500C11B20 /* pb_gpt_bot.gif */; };
 		CD2B01242D43961E00C11B20 /* QRProfileController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD2B01222D43961E00C11B20 /* QRProfileController.swift */; };
 		CD2B01252D43961E00C11B20 /* QRScannerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD2B01232D43961E00C11B20 /* QRScannerController.swift */; };
@@ -221,9 +235,6 @@
 		CDDF46752A2DD81300049A19 /* CreateSeminarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDDF46732A2DD81300049A19 /* CreateSeminarViewController.swift */; };
 		CDDF46762A2DD81300049A19 /* SeminarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDDF46742A2DD81300049A19 /* SeminarViewController.swift */; };
 		CDDF467A2A2EF0A700049A19 /* ScreenSharingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDDF46792A2EF0A700049A19 /* ScreenSharingViewController.swift */; };
-		CDE3BED92C86E3A000B0BF36 /* pb_call_in.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = CDE3BED62C86E3A000B0BF36 /* pb_call_in.mp3 */; };
-		CDE3BEDA2C86E3A000B0BF36 /* pb_call_out.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = CDE3BED72C86E3A000B0BF36 /* pb_call_out.mp3 */; };
-		D31B80FB0B180E56A4404716 /* Pods_NexilisLite_NexilisLiteTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A251D070A810111946C8B53 /* Pods_NexilisLite_NexilisLiteTests.framework */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -257,14 +268,9 @@
 /* End PBXCopyFilesBuildPhase section */
 
 /* Begin PBXFileReference section */
-		1110A3F5E0271C679F1F19AB /* Pods-NexilisLite-NexilisLiteTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NexilisLite-NexilisLiteTests.debug.xcconfig"; path = "Target Support Files/Pods-NexilisLite-NexilisLiteTests/Pods-NexilisLite-NexilisLiteTests.debug.xcconfig"; sourceTree = "<group>"; };
 		1241AEBA2D017E8C0088175A /* FileEncryption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileEncryption.swift; sourceTree = "<group>"; };
 		1241AEBB2D017E8C0088175A /* MasterKeyUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterKeyUtil.swift; sourceTree = "<group>"; };
 		12C36CEA2D02995F0095BEC1 /* SecureFolderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureFolderView.swift; sourceTree = "<group>"; };
-		37586DF5C0C7BDEE46DD9B4D /* Pods-NexilisLite.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NexilisLite.release.xcconfig"; path = "Target Support Files/Pods-NexilisLite/Pods-NexilisLite.release.xcconfig"; sourceTree = "<group>"; };
-		5D33EC4921048C8C0720CDEC /* Pods-NexilisLite.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NexilisLite.debug.xcconfig"; path = "Target Support Files/Pods-NexilisLite/Pods-NexilisLite.debug.xcconfig"; sourceTree = "<group>"; };
-		8A251D070A810111946C8B53 /* Pods_NexilisLite_NexilisLiteTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NexilisLite_NexilisLiteTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
-		B3B2ED0FC8F57DC0267CCB22 /* Pods-NexilisLite-NexilisLiteTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NexilisLite-NexilisLiteTests.release.xcconfig"; path = "Target Support Files/Pods-NexilisLite-NexilisLiteTests/Pods-NexilisLite-NexilisLiteTests.release.xcconfig"; sourceTree = "<group>"; };
 		CD1E6E682A0B7C3600BF871F /* NexilisLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = NexilisLite.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		CD1E6E6B2A0B7C3600BF871F /* NexilisLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NexilisLite.h; sourceTree = "<group>"; };
 		CD1E6E6C2A0B7C3600BF871F /* NexilisLite.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = NexilisLite.docc; sourceTree = "<group>"; };
@@ -370,6 +376,19 @@
 		CD1E79142A0CA43600BF871F /* SDWebImage.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SDWebImage.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		CD1E79152A0CA43600BF871F /* SnapKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SnapKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		CD1E79162A0CA43600BF871F /* Toast_Swift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Toast_Swift.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+		CD2585B72D59DA60002AB416 /* Dutifully.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = Dutifully.mp3; sourceTree = "<group>"; };
+		CD2585B82D59DA60002AB416 /* Elegant.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = Elegant.mp3; sourceTree = "<group>"; };
+		CD2585B92D59DA60002AB416 /* Eventually.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = Eventually.mp3; sourceTree = "<group>"; };
+		CD2585BA2D59DA60002AB416 /* Hangover.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = Hangover.mp3; sourceTree = "<group>"; };
+		CD2585BB2D59DA60002AB416 /* Juntos.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = Juntos.mp3; sourceTree = "<group>"; };
+		CD2585BC2D59DA60002AB416 /* Light_Hearted.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = Light_Hearted.mp3; sourceTree = "<group>"; };
+		CD2585BD2D59DA60002AB416 /* Magic.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = Magic.mp3; sourceTree = "<group>"; };
+		CD2585BE2D59DA60002AB416 /* Nexilis_Message.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = Nexilis_Message.mp3; sourceTree = "<group>"; };
+		CD2585BF2D59DA60002AB416 /* Out_of_Nowhere.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = Out_of_Nowhere.mp3; sourceTree = "<group>"; };
+		CD2585C02D59DA60002AB416 /* Relax.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = Relax.mp3; sourceTree = "<group>"; };
+		CD2585C12D59DA60002AB416 /* Strong_Minded.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = Strong_Minded.mp3; sourceTree = "<group>"; };
+		CD2585C22D59DA60002AB416 /* Swift_gesture.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = Swift_gesture.mp3; sourceTree = "<group>"; };
+		CD2585C32D59DA60002AB416 /* Upset.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = Upset.mp3; sourceTree = "<group>"; };
 		CD2B01202D43945500C11B20 /* pb_gpt_bot.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = pb_gpt_bot.gif; sourceTree = "<group>"; };
 		CD2B01222D43961E00C11B20 /* QRProfileController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRProfileController.swift; sourceTree = "<group>"; };
 		CD2B01232D43961E00C11B20 /* QRScannerController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRScannerController.swift; sourceTree = "<group>"; };
@@ -488,7 +507,6 @@
 		CDDF46792A2EF0A700049A19 /* ScreenSharingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScreenSharingViewController.swift; sourceTree = "<group>"; };
 		CDE3BED62C86E3A000B0BF36 /* pb_call_in.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = pb_call_in.mp3; sourceTree = "<group>"; };
 		CDE3BED72C86E3A000B0BF36 /* pb_call_out.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = pb_call_out.mp3; sourceTree = "<group>"; };
-		FCEE47B4987D63B825DAFB26 /* Pods_NexilisLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NexilisLite.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -496,7 +514,6 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				A26061F6D200295A7E5689A6 /* Pods_NexilisLite.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -505,7 +522,6 @@
 			buildActionMask = 2147483647;
 			files = (
 				CD1E6E732A0B7C3600BF871F /* NexilisLite.framework in Frameworks */,
-				D31B80FB0B180E56A4404716 /* Pods_NexilisLite_NexilisLiteTests.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -529,8 +545,6 @@
 				CD1E79142A0CA43600BF871F /* SDWebImage.framework */,
 				CD1E79152A0CA43600BF871F /* SnapKit.framework */,
 				CD1E79162A0CA43600BF871F /* Toast_Swift.framework */,
-				FCEE47B4987D63B825DAFB26 /* Pods_NexilisLite.framework */,
-				8A251D070A810111946C8B53 /* Pods_NexilisLite_NexilisLiteTests.framework */,
 			);
 			name = Frameworks;
 			sourceTree = "<group>";
@@ -539,10 +553,6 @@
 			isa = PBXGroup;
 			children = (
 				CD46A0BE2A0CE4FD009E4C87 /* NexilisLite.podspec */,
-				5D33EC4921048C8C0720CDEC /* Pods-NexilisLite.debug.xcconfig */,
-				37586DF5C0C7BDEE46DD9B4D /* Pods-NexilisLite.release.xcconfig */,
-				1110A3F5E0271C679F1F19AB /* Pods-NexilisLite-NexilisLiteTests.debug.xcconfig */,
-				B3B2ED0FC8F57DC0267CCB22 /* Pods-NexilisLite-NexilisLiteTests.release.xcconfig */,
 			);
 			path = Pods;
 			sourceTree = "<group>";
@@ -910,6 +920,19 @@
 		CDE3BED82C86E3A000B0BF36 /* Sound */ = {
 			isa = PBXGroup;
 			children = (
+				CD2585B72D59DA60002AB416 /* Dutifully.mp3 */,
+				CD2585B82D59DA60002AB416 /* Elegant.mp3 */,
+				CD2585B92D59DA60002AB416 /* Eventually.mp3 */,
+				CD2585BA2D59DA60002AB416 /* Hangover.mp3 */,
+				CD2585BB2D59DA60002AB416 /* Juntos.mp3 */,
+				CD2585BC2D59DA60002AB416 /* Light_Hearted.mp3 */,
+				CD2585BD2D59DA60002AB416 /* Magic.mp3 */,
+				CD2585BE2D59DA60002AB416 /* Nexilis_Message.mp3 */,
+				CD2585BF2D59DA60002AB416 /* Out_of_Nowhere.mp3 */,
+				CD2585C02D59DA60002AB416 /* Relax.mp3 */,
+				CD2585C12D59DA60002AB416 /* Strong_Minded.mp3 */,
+				CD2585C22D59DA60002AB416 /* Swift_gesture.mp3 */,
+				CD2585C32D59DA60002AB416 /* Upset.mp3 */,
 				CDE3BED62C86E3A000B0BF36 /* pb_call_in.mp3 */,
 				CDE3BED72C86E3A000B0BF36 /* pb_call_out.mp3 */,
 			);
@@ -934,13 +957,11 @@
 			isa = PBXNativeTarget;
 			buildConfigurationList = CD1E6E7C2A0B7C3600BF871F /* Build configuration list for PBXNativeTarget "NexilisLite" */;
 			buildPhases = (
-				A61B618FF1E7B362295C06F8 /* [CP] Check Pods Manifest.lock */,
 				CD1E6E632A0B7C3600BF871F /* Headers */,
 				CD1E6E642A0B7C3600BF871F /* Sources */,
 				CD1E6E652A0B7C3600BF871F /* Frameworks */,
 				CD1E6E662A0B7C3600BF871F /* Resources */,
 				CD469E162A0CA6C9009E4C87 /* Embed Frameworks */,
-				6445862B9592937F2B29E34F /* [CP] Copy Pods Resources */,
 			);
 			buildRules = (
 			);
@@ -956,12 +977,9 @@
 			isa = PBXNativeTarget;
 			buildConfigurationList = CD1E6E7F2A0B7C3600BF871F /* Build configuration list for PBXNativeTarget "NexilisLiteTests" */;
 			buildPhases = (
-				E63ED6260299A6F86290412B /* [CP] Check Pods Manifest.lock */,
 				CD1E6E6E2A0B7C3600BF871F /* Sources */,
 				CD1E6E6F2A0B7C3600BF871F /* Frameworks */,
 				CD1E6E702A0B7C3600BF871F /* Resources */,
-				8759B98BCDF4B0E24ED86A1C /* [CP] Embed Pods Frameworks */,
-				6786B5FCF1438FB2EE07EB73 /* [CP] Copy Pods Resources */,
 			);
 			buildRules = (
 			);
@@ -1038,13 +1056,11 @@
 			buildActionMask = 2147483647;
 			files = (
 				CD46A06F2A0CE310009E4C87 /* NexilisLiteResources.bundle in Resources */,
-				CDE3BEDA2C86E3A000B0BF36 /* pb_call_out.mp3 in Resources */,
 				CD46A06B2A0CE307009E4C87 /* Localizable.strings in Resources */,
 				CD46A06C2A0CE307009E4C87 /* Assets.xcassets in Resources */,
 				CD46A06D2A0CE307009E4C87 /* PreviewAttachmentImageVideo.xib in Resources */,
 				CD46A06E2A0CE307009E4C87 /* Palio.storyboard in Resources */,
 				CD46A0BF2A0CE4FD009E4C87 /* NexilisLite.podspec in Resources */,
-				CDE3BED92C86E3A000B0BF36 /* pb_call_in.mp3 in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1059,8 +1075,22 @@
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				CD2585D22D59DA79002AB416 /* pb_call_out.mp3 in Resources */,
 				CD46A0702A0CE320009E4C87 /* sticker_10000000_5.png in Resources */,
 				CD46A0712A0CE320009E4C87 /* sticker_20000000_22.png in Resources */,
+				CD2585C42D59DA60002AB416 /* Dutifully.mp3 in Resources */,
+				CD2585C52D59DA60002AB416 /* Juntos.mp3 in Resources */,
+				CD2585C62D59DA60002AB416 /* Light_Hearted.mp3 in Resources */,
+				CD2585C72D59DA60002AB416 /* Nexilis_Message.mp3 in Resources */,
+				CD2585C82D59DA60002AB416 /* Out_of_Nowhere.mp3 in Resources */,
+				CD2585C92D59DA60002AB416 /* Swift_gesture.mp3 in Resources */,
+				CD2585CA2D59DA60002AB416 /* Relax.mp3 in Resources */,
+				CD2585CB2D59DA60002AB416 /* Eventually.mp3 in Resources */,
+				CD2585CC2D59DA60002AB416 /* Upset.mp3 in Resources */,
+				CD2585CD2D59DA60002AB416 /* Elegant.mp3 in Resources */,
+				CD2585CE2D59DA60002AB416 /* Strong_Minded.mp3 in Resources */,
+				CD2585CF2D59DA60002AB416 /* Magic.mp3 in Resources */,
+				CD2585D02D59DA60002AB416 /* Hangover.mp3 in Resources */,
 				CD46A0722A0CE320009E4C87 /* sticker_30000000_38.png in Resources */,
 				CD46A0732A0CE320009E4C87 /* sticker_30000000_10.png in Resources */,
 				CD46A0742A0CE320009E4C87 /* sticker_30000000_11.png in Resources */,
@@ -1157,109 +1187,12 @@
 				CD46A0B92A0CE320009E4C87 /* sticker_20000000_13.png in Resources */,
 				CD46A0BA2A0CE320009E4C87 /* sticker_20000000_0.png in Resources */,
 				CD46A0BB2A0CE320009E4C87 /* sticker_10000000_8.png in Resources */,
+				CD2585D12D59DA73002AB416 /* pb_call_in.mp3 in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 /* End PBXResourcesBuildPhase section */
 
-/* Begin PBXShellScriptBuildPhase section */
-		6445862B9592937F2B29E34F /* [CP] Copy Pods Resources */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputFileListPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-NexilisLite/Pods-NexilisLite-resources-${CONFIGURATION}-input-files.xcfilelist",
-			);
-			name = "[CP] Copy Pods Resources";
-			outputFileListPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-NexilisLite/Pods-NexilisLite-resources-${CONFIGURATION}-output-files.xcfilelist",
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NexilisLite/Pods-NexilisLite-resources.sh\"\n";
-			showEnvVarsInLog = 0;
-		};
-		6786B5FCF1438FB2EE07EB73 /* [CP] Copy Pods Resources */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputFileListPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-NexilisLite-NexilisLiteTests/Pods-NexilisLite-NexilisLiteTests-resources-${CONFIGURATION}-input-files.xcfilelist",
-			);
-			name = "[CP] Copy Pods Resources";
-			outputFileListPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-NexilisLite-NexilisLiteTests/Pods-NexilisLite-NexilisLiteTests-resources-${CONFIGURATION}-output-files.xcfilelist",
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NexilisLite-NexilisLiteTests/Pods-NexilisLite-NexilisLiteTests-resources.sh\"\n";
-			showEnvVarsInLog = 0;
-		};
-		8759B98BCDF4B0E24ED86A1C /* [CP] Embed Pods Frameworks */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputFileListPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-NexilisLite-NexilisLiteTests/Pods-NexilisLite-NexilisLiteTests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
-			);
-			name = "[CP] Embed Pods Frameworks";
-			outputFileListPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-NexilisLite-NexilisLiteTests/Pods-NexilisLite-NexilisLiteTests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NexilisLite-NexilisLiteTests/Pods-NexilisLite-NexilisLiteTests-frameworks.sh\"\n";
-			showEnvVarsInLog = 0;
-		};
-		A61B618FF1E7B362295C06F8 /* [CP] Check Pods Manifest.lock */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputFileListPaths = (
-			);
-			inputPaths = (
-				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
-				"${PODS_ROOT}/Manifest.lock",
-			);
-			name = "[CP] Check Pods Manifest.lock";
-			outputFileListPaths = (
-			);
-			outputPaths = (
-				"$(DERIVED_FILE_DIR)/Pods-NexilisLite-checkManifestLockResult.txt",
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
-			showEnvVarsInLog = 0;
-		};
-		E63ED6260299A6F86290412B /* [CP] Check Pods Manifest.lock */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputFileListPaths = (
-			);
-			inputPaths = (
-				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
-				"${PODS_ROOT}/Manifest.lock",
-			);
-			name = "[CP] Check Pods Manifest.lock";
-			outputFileListPaths = (
-			);
-			outputPaths = (
-				"$(DERIVED_FILE_DIR)/Pods-NexilisLite-NexilisLiteTests-checkManifestLockResult.txt",
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
-			showEnvVarsInLog = 0;
-		};
-/* End PBXShellScriptBuildPhase section */
-
 /* Begin PBXSourcesBuildPhase section */
 		CD1E6E642A0B7C3600BF871F /* Sources */ = {
 			isa = PBXSourcesBuildPhase;
@@ -1541,7 +1474,6 @@
 		};
 		CD1E6E7D2A0B7C3600BF871F /* Debug */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 5D33EC4921048C8C0720CDEC /* Pods-NexilisLite.debug.xcconfig */;
 			buildSettings = {
 				BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
 				CODE_SIGN_STYLE = Automatic;
@@ -1581,7 +1513,6 @@
 		};
 		CD1E6E7E2A0B7C3600BF871F /* Release */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 37586DF5C0C7BDEE46DD9B4D /* Pods-NexilisLite.release.xcconfig */;
 			buildSettings = {
 				BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
 				CODE_SIGN_STYLE = Automatic;
@@ -1621,7 +1552,6 @@
 		};
 		CD1E6E802A0B7C3600BF871F /* Debug */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 1110A3F5E0271C679F1F19AB /* Pods-NexilisLite-NexilisLiteTests.debug.xcconfig */;
 			buildSettings = {
 				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)";
 				CODE_SIGN_STYLE = Automatic;
@@ -1639,7 +1569,6 @@
 		};
 		CD1E6E812A0B7C3600BF871F /* Release */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = B3B2ED0FC8F57DC0267CCB22 /* Pods-NexilisLite-NexilisLiteTests.release.xcconfig */;
 			buildSettings = {
 				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)";
 				CODE_SIGN_STYLE = Automatic;

TEMPAT SAMPAH
NexilisLite/NexilisLite/.DS_Store


TEMPAT SAMPAH
NexilisLite/NexilisLite/Resource/.DS_Store


TEMPAT SAMPAH
NexilisLite/NexilisLite/Resource/Sound/Dutifully.mp3


TEMPAT SAMPAH
NexilisLite/NexilisLite/Resource/Sound/Elegant.mp3


TEMPAT SAMPAH
NexilisLite/NexilisLite/Resource/Sound/Eventually.mp3


TEMPAT SAMPAH
NexilisLite/NexilisLite/Resource/Sound/Hangover.mp3


TEMPAT SAMPAH
NexilisLite/NexilisLite/Resource/Sound/Juntos.mp3


TEMPAT SAMPAH
NexilisLite/NexilisLite/Resource/Sound/Light_Hearted.mp3


TEMPAT SAMPAH
NexilisLite/NexilisLite/Resource/Sound/Magic.mp3


TEMPAT SAMPAH
NexilisLite/NexilisLite/Resource/Sound/Nexilis_Message.mp3


TEMPAT SAMPAH
NexilisLite/NexilisLite/Resource/Sound/Out_of_Nowhere.mp3


TEMPAT SAMPAH
NexilisLite/NexilisLite/Resource/Sound/Relax.mp3


TEMPAT SAMPAH
NexilisLite/NexilisLite/Resource/Sound/Strong_Minded.mp3


TEMPAT SAMPAH
NexilisLite/NexilisLite/Resource/Sound/Swift_gesture.mp3


TEMPAT SAMPAH
NexilisLite/NexilisLite/Resource/Sound/Upset.mp3


+ 2 - 0
NexilisLite/NexilisLite/Resource/id.lproj/Localizable.strings

@@ -389,3 +389,5 @@
 "Unable to complete action" = "Gagal";
 "Successfully added a new friend" = "Teman baru berhasil ditambahkan";
 "Friend is invalid or not found" = "Teman tidak valid atau tidak ditemukan";
+"Warning Unknown Url!" = "Peringatan Url Tidak Dikenal!";
+"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" = "Anda akan mengakses situs web yang saat ini tidak dipercaya oleh Nexilis Browser Anda. Sertifikat keamanan situs web ini tidak dikenali.\n\nApakah Anda ingin melanjutkan ke <<domain>> dan mempercayai sertifikat keamanan situs web tersebut?\n\nCatatan: Menambahkan situs web ke daftar tepercaya dapat meningkatkan risiko kerentanan keamanan Anda";

+ 20 - 17
NexilisLite/NexilisLite/Source/APIS.swift

@@ -875,7 +875,7 @@ public class APIS: NSObject {
     //                                            print("Incoming call reported successfully")
     //                                        }
     //                                    }
-                                copySoundToLocalPath()
+                                copySoundToLocalPath("pb_call_in")
                                 let center = UNUserNotificationCenter.current()
                                 let content = UNMutableNotificationContent()
                                 content.title = callFromName
@@ -970,6 +970,7 @@ public class APIS: NSObject {
         if scope == "3" || scope == "18" || scope == "5"{
             type = "0"
         }
+        var soundId: String = SecureUserDefaults.shared.value(forKey: "newNotifSoundPersonal") ?? "001:Nexilis Message (Default)"
         if type == "1" {
             Database.shared.database?.inTransaction({ (fmdb, rollback) in
                 do {
@@ -991,14 +992,19 @@ public class APIS: NSObject {
                     print("Access database error: \(error.localizedDescription)")
                 }
             })
+            soundId = SecureUserDefaults.shared.value(forKey: "newNotifSoundGroup") ?? "001:Nexilis Message (Default)"
             if idGroup.isEmpty {
                 return
             }
         }
+        var nameSound = soundId.components(separatedBy: ":")[1].replacingOccurrences(of: " ", with: "_")
+        if nameSound.contains("_(Default)") {
+            nameSound = nameSound.replacingOccurrences(of: "_(Default)", with: "")
+        }
+        copySoundToLocalPath(nameSound)
         let center = UNUserNotificationCenter.current()
         let content = UNMutableNotificationContent()
         content.title = nameUser
-        content.sound = .default
         if type == "1" {
             content.body = text.richText(group_id: idGroup).string
             content.subtitle = nameSubtitle
@@ -1006,7 +1012,7 @@ public class APIS: NSObject {
             content.body = text.richText().string
         }
         content.userInfo = ["id" : threadIdentifier, "type" : type]
-        content.sound = UNNotificationSound(named: UNNotificationSoundName("pb_call_in.mp3"))
+        content.sound = UNNotificationSound(named: UNNotificationSoundName("\(nameSound).mp3"))
         let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
         let request = UNNotificationRequest(identifier: messageId, content: content, trigger: trigger)
         center.add(request) { error in
@@ -1014,19 +1020,18 @@ public class APIS: NSObject {
                 print("Error scheduling notification: \(error.localizedDescription)")
             }
         }
-        UIApplication.shared.applicationIconBadgeNumber = Int(APIS.getTotalCounter())
+        DispatchQueue.main.async {
+            UIApplication.shared.applicationIconBadgeNumber = Int(APIS.getTotalCounter())
+        }
     }
     
-    private static func copySoundToLocalPath() {
-        guard let sourceURL = Bundle.resourceBundle(for: Nexilis.self).url(forResource: "pb_call_in", withExtension: "mp3") else {
-            return
+    private static func copySoundToLocalPath(_ nameSound: String) {
+        var sourceURL = Bundle.resourceBundle(for: Nexilis.self).url(forResource: nameSound, withExtension: "mp3")
+        if sourceURL == nil {
+            sourceURL = Bundle.resourcesMediaBundle(for: Nexilis.self).url(forResource: nameSound, withExtension: "mp3")
         }
-
-        // Define the destination path in the Library/Sounds directory
         let fileManager = FileManager.default
         let soundDirectory = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first!.appendingPathComponent("Sounds", isDirectory: true)
-
-        // Ensure the Sounds directory exists
         if !fileManager.fileExists(atPath: soundDirectory.path) {
             do {
                 try fileManager.createDirectory(at: soundDirectory, withIntermediateDirectories: true, attributes: nil)
@@ -1035,11 +1040,10 @@ public class APIS: NSObject {
                 return
             }
         }
-
-        let destinationURL = soundDirectory.appendingPathComponent("pb_call_in.mp3")
+        let destinationURL = soundDirectory.appendingPathComponent("\(nameSound).mp3")
         if !fileManager.fileExists(atPath: destinationURL.path) {
             do {
-                try fileManager.copyItem(at: sourceURL, to: destinationURL)
+                try fileManager.copyItem(at: sourceURL!, to: destinationURL)
             } catch {
                 
             }
@@ -1093,7 +1097,6 @@ public class APIS: NSObject {
                                 idGroup = id
                             }
                             if let cursorGroup = Database.shared.getRecords(fmdb: fmdb, query: "SELECT f_name, image_id FROM GROUPZ WHERE group_id='\(idGroup)'"), cursorGroup.next() {
-                                let nameGroup = cursorGroup.string(forColumnIndex: 0) ?? ""
                                 groupExist = true
                                 cursorGroup.close()
                             }
@@ -1195,7 +1198,7 @@ public class APIS: NSObject {
         case .active:
             return false
         case .inactive:
-            return true
+            return false
         case .background:
             return true
         @unknown default:
@@ -1208,7 +1211,7 @@ public class APIS: NSObject {
 //            try API.switchCBI(cbiI: Callback(), bLight: true)
 //        } catch {
 //        }
-        exit(0)
+//        exit(0)
     }
     
     public static func enterForeground() {

+ 46 - 4
NexilisLite/NexilisLite/Source/Callback.swift

@@ -9,6 +9,7 @@
 import Foundation
 import nuSDKService
 import Network
+import NotificationBannerSwift
 
 class Callback : CallBack {
     var sID: String = "Callback"
@@ -143,6 +144,8 @@ class NetworkMonitor {
     
     private let monitor = NWPathMonitor()
     private var isMonitoring = false
+    var isConnected: Bool = false
+    var fromDisconnect = false
     
     private init() {}
     
@@ -150,12 +153,32 @@ class NetworkMonitor {
         guard !isMonitoring else { return }
         
         monitor.pathUpdateHandler = { path in
-            //print("CONNECTION \(path.status == .satisfied)")
-            InquiryThread.default.set(wait: !(path.status == .satisfied))
-            OutgoingThread.default.set(wait: !(path.status == .satisfied))
+            self.isConnected = path.status == .satisfied
+            self.canAccessGoogle(completion: { [self] connected in
+                InquiryThread.default.set(wait: !connected)
+                OutgoingThread.default.set(wait: !connected)
+                if !connected {
+                    fromDisconnect = true
+                    DispatchQueue.main.async {
+                        let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
+                        imageView.tintColor = .white
+                        let banner = FloatingNotificationBanner(title: "Check your connection".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
+                        banner.show()
+                    }
+                }
+                if connected && fromDisconnect {
+                    fromDisconnect = false
+                    DispatchQueue.main.async {
+                        let imageView = UIImageView(image: UIImage(systemName: "checkmark.circle.fill"))
+                        imageView.tintColor = .white
+                        let banner = FloatingNotificationBanner(title: "You're Connected".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .success, colors: nil, iconPosition: .center)
+                        banner.show()
+                    }
+                }
+            })
         }
         
-        let queue = DispatchQueue(label: "NetworkMonitor")
+        let queue = DispatchQueue.global(qos: .background)
         monitor.start(queue: queue)
         isMonitoring = true
     }
@@ -165,4 +188,23 @@ class NetworkMonitor {
         monitor.cancel()
         isMonitoring = false
     }
+    
+    func canAccessGoogle(completion: @escaping (Bool) -> Void) {
+        guard isConnected, let url = URL(string: "https://www.google.com") else {
+            completion(false)
+            return
+        }
+        
+        var request = URLRequest(url: url)
+        request.timeoutInterval = 5
+        
+        let task = URLSession.shared.dataTask(with: request) { _, response, error in
+            if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 {
+                completion(true)
+            } else {
+                completion(false)
+            }
+        }
+        task.resume()
+    }
 }

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

@@ -1266,12 +1266,10 @@ class IncomingThread {
         }
     }
     
-    public func receiveMessage(message: TMessage, withoutACK: Bool = false) -> Void {
+    private func receiveMessage(message: TMessage) -> Void {
         guard let _: String = SecureUserDefaults.shared.value(forKey: "status") else {
             //print("App not ready!!! skip receive message \(message_id)")
-            if !withoutACK {
-                ack(message: message)
-            }
+            ack(message: message)
             return
         }
         let media = message.getMedia()
@@ -1313,9 +1311,7 @@ class IncomingThread {
             }
         }
         //print("save message incoming")
-        if !withoutACK {
-            ack(message: message)
-        }
+        ack(message: message)
     }
     
     private func receiveMessageStatus(message: TMessage) -> Void {

+ 2 - 2
NexilisLite/NexilisLite/Source/Model/Chat.swift

@@ -137,9 +137,9 @@ public class Chat: Model {
                 var query = """
                             select m.f_pin, ms.l_pin, ms.message_id, ms.counter, m.message_text, m.server_date, m.image_id, m.video_id, m.file_id, m.attachment_flag, m.message_scope_id, b.first_name || ' ' || ifnull(b.last_name, '') name, b.image_id profile, b.official_account, m.status, m.credential, m.lock, m.audio_id, m.gif_id from MESSAGE_SUMMARY ms, MESSAGE m, BUDDY b where ms.l_pin = b.f_pin and ms.message_id = m.message_id \(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'") and m.is_call_center = 0
                             union
-                            select m.f_pin, ms.l_pin, ms.message_id, ms.counter, m.message_text, m.server_date, m.image_id, m.video_id, m.file_id, m.attachment_flag, m.message_scope_id, 'Bot' name, '' profile, '', m.status, m.credential, m.lock, m.audio_id, m.gif_id from MESSAGE_SUMMARY ms, MESSAGE m where ms.l_pin = '-999' and "ms.message_id = m.message_id \(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'")" and m.is_call_center = 0
+                            select m.f_pin, ms.l_pin, ms.message_id, ms.counter, m.message_text, m.server_date, m.image_id, m.video_id, m.file_id, m.attachment_flag, m.message_scope_id, 'Bot' name, '' profile, '', m.status, m.credential, m.lock, m.audio_id, m.gif_id from MESSAGE_SUMMARY ms, MESSAGE m where ms.l_pin = '-999' and ms.message_id = m.message_id\(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'")
                             union
-                            select m.f_pin, ms.l_pin, ms.message_id, ms.counter, m.message_text, m.server_date, m.image_id, m.video_id, m.file_id, m.attachment_flag, m.message_scope_id, 'GPT SmartBot' name, '' profile, '', m.status, m.credential, m.lock, m.audio_id, m.gif_id from MESSAGE_SUMMARY ms, MESSAGE m where ms.l_pin = '-997' and "ms.message_id = m.message_id \(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'")" and m.is_call_center = 0
+                            select m.f_pin, ms.l_pin, ms.message_id, ms.counter, m.message_text, m.server_date, m.image_id, m.video_id, m.file_id, m.attachment_flag, m.message_scope_id, 'GPT SmartBot' name, '' profile, '', m.status, m.credential, m.lock, m.audio_id, m.gif_id from MESSAGE_SUMMARY ms, MESSAGE m where ms.l_pin = '-997' and ms.message_id = m.message_id\(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'")
                             union
                             select m.f_pin, ms.l_pin, ms.message_id, ms.counter, m.message_text, m.server_date, m.image_id, m.video_id, m.file_id, m.attachment_flag, m.message_scope_id, b.f_name || ' (\("Lounge".localized()))', b.image_id profile, b.official, m.status, m.credential, m.lock, m.audio_id, m.gif_id from MESSAGE_SUMMARY ms, MESSAGE m, GROUPZ b where ms.l_pin = b.group_id and ms.message_id = m.message_id \(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'") and m.is_call_center = 0
                             union

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

@@ -124,6 +124,7 @@ public class Nexilis: NSObject {
     
     static var ringtonePlayer: AVAudioPlayer?
     static var ringbacktonePlayer: AVAudioPlayer?
+    static var sharedAudioPlayer: AVAudioPlayer?
     
     private func createDelegate() {
         //print("createDelegate...")
@@ -147,7 +148,13 @@ public class Nexilis: NSObject {
         do {
             try MasterKeyUtil.shared.generateAndStoreMasterKey()
             try MasterKeyUtil.shared.generateAndStorePrefsKey()
-            Utils.setCertificatePinningWebview(value: Utils.decrypt(str: "6]qaAhNXHAxGKAOsFZmoIRUIn2Hp3rdfGAOFEfcnY4SIZ"))
+            if Utils.getCertificatePinningWebview().isEmpty {
+                let cert: [String: String] = ["nexilis.io": Utils.decrypt(str: "7^reFspLRnRz3NaVjeI2AUQ0l5JFQbf0bZZ3dfYaBMqnL"), "newuniverse.io": Utils.decrypt(str: "6]umyRKg9l6D2N?2wVaejmtPrWNtVKpjqt0mqyA68XwFi")]
+                if let jsonData = try? JSONSerialization.data(withJSONObject: cert, options: []),
+                   let jsonString = String(data: jsonData, encoding: .utf8) {
+                    Utils.setCertificatePinningWebview(value: jsonString)
+                }
+            }
         } catch {
         }
         
@@ -3815,27 +3822,25 @@ extension Nexilis: MessageDelegate {
                     }
                     
                     floating.show(queuePosition: .front, bannerPosition: .top, queue: NotificationBannerQueue(maxBannersOnScreenSimultaneously: 1), on: nil, edgeInsets: UIEdgeInsets(top: 8.0, left: 8.0, bottom: 0, right: 8.0), cornerRadius: 8.0, shadowColor: .clear, shadowOpacity: .zero, shadowBlurRadius: .zero, shadowCornerRadius: .zero, shadowOffset: .zero, shadowEdgeInsets: nil)
-                    let vibrateMode: Bool = SecureUserDefaults.shared.value(forKey: "vibrateMode") ?? false
-                    var soundId: String = SecureUserDefaults.shared.value(forKey: "notifSoundPersonal") ?? ""
+//                    let vibrateMode: Bool = SecureUserDefaults.shared.value(forKey: "vibrateMode") ?? false
+                    var soundId: String = SecureUserDefaults.shared.value(forKey: "newNotifSoundPersonal") ?? "001:Nexilis Message (Default)"
                     if message.getBody(key: CoreMessage_TMessageKey.MESSAGE_SCOPE_ID) == "4" {
-                        soundId = SecureUserDefaults.shared.value(forKey: "notifSoundGroup") ?? ""
+                        soundId = SecureUserDefaults.shared.value(forKey: "newNotifSoundGroup") ?? "001:Nexilis Message (Default)"
                     }
-                    var systemSoundID: SystemSoundID!
-                    if soundId.isEmpty {
-                        systemSoundID = 1007
-                    } else {
-                        systemSoundID = SystemSoundID(Int(soundId.components(separatedBy: ":")[0])!)
-                    }
-                    if vibrateMode {
-                        AudioServicesPlaySystemSound(systemSoundID)
-                    } else {
-                        var nameSound = "sms-received1"
-                        if systemSoundID != 1007 {
-                            nameSound = soundId.components(separatedBy: ":")[1]
+                    do {
+                        var nameSound = soundId.components(separatedBy: ":")[1].replacingOccurrences(of: " ", with: "_")
+                        if nameSound.contains("_(Default)") {
+                            nameSound = nameSound.replacingOccurrences(of: "_(Default)", with: "")
+                        }
+                        var soundURL = Bundle.resourceBundle(for: Nexilis.self).url(forResource: nameSound, withExtension: "mp3")
+                        if soundURL == nil {
+                            soundURL = Bundle.resourcesMediaBundle(for: Nexilis.self).url(forResource: nameSound, withExtension: "mp3")
                         }
-                        let url = URL(fileURLWithPath: "/System/Library/Audio/UISounds/\(nameSound).caf")
-                        AudioServicesCreateSystemSoundID(url as CFURL, &systemSoundID)
-                        AudioServicesPlaySystemSound(systemSoundID)
+                        Nexilis.sharedAudioPlayer = try AVAudioPlayer(contentsOf: soundURL!)
+                        Nexilis.sharedAudioPlayer?.prepareToPlay()
+                        Nexilis.sharedAudioPlayer?.play()
+                    } catch {
+                        
                     }
                     if !onGoingCC.isEmpty {
                         floating.autoDismiss = false

+ 4 - 6
NexilisLite/NexilisLite/Source/OutgoingThread.swift

@@ -180,9 +180,10 @@ class OutgoingThread {
                                 if let delegate = Nexilis.shared.messageDelegate {
                                     delegate.onUpload(name: fileName, progress: progress)
                                 }
+                                print("progress upload", progress)
                                 if progress == 100 {
                                     if let response = Nexilis.writeSync(message: message) {
-                                        //print("sendChat", response.toLogString())
+                                        print("sendChat", response.toLogString())
                                         let messageId = response.getBody(key: CoreMessage_TMessageKey.MESSAGE_ID)
                                         Database.shared.database?.inTransaction({ (fmdb, rollback) in
                                             do {
@@ -391,12 +392,9 @@ class OutgoingThread {
                         if !chat.isEmpty {
                             pin = chat
                         }
-                        var queryGetLastMessageId = "SELECT message_id FROM MESSAGE where f_pin = '\(pin)' OR l_pin = '\(pin)' order by server_date desc LIMIT 1"
+                        var queryGetLastMessageId = "SELECT message_id FROM MESSAGE where (f_pin = '\(pin)' OR l_pin = '\(pin)') AND message_scope_id = '3' order by server_date desc LIMIT 1"
                         if scope == "4" {
-                            queryGetLastMessageId = "SELECT message_id FROM MESSAGE where l_pin = '\(pin)' AND chat_id = '' order by server_date desc LIMIT 1"
-                            if !chat.isEmpty {
-                                queryGetLastMessageId = "SELECT message_id FROM MESSAGE where chat_id = '\(pin)' order by server_date desc LIMIT 1"
-                            }
+                            queryGetLastMessageId = "SELECT message_id FROM MESSAGE where l_pin = '\(chat.isEmpty ? pin : l_pin)' AND chat_id = '\(chat)' AND message_scope_id = '4' order by server_date desc LIMIT 1"
                         }
                         var messageId = ""
                         if let cursorData = Database.shared.getRecords(fmdb: fmdb, query: queryGetLastMessageId), cursorData.next() {

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

@@ -359,12 +359,12 @@ public final class Utils {
         return dateFormatter.string(from: todaysDate as Date)
     }
     
-    static func setCertificatePinningWebview(value: String) {
-        SecureUserDefaults.shared.set(value, forKey: "certificate_pinning_webview")
+    public static func setCertificatePinningWebview(value: String) {
+        SecureUserDefaults.shared.set(value, forKey: "pb_certificate_pinning_webview")
     }
 
     public static func getCertificatePinningWebview() -> String {
-        if let value: String = SecureUserDefaults.shared.value(forKey: "certificate_pinning_webview") {
+        if let value: String = SecureUserDefaults.shared.value(forKey: "pb_certificate_pinning_webview") {
             return value
         }
         return ""

+ 104 - 40
NexilisLite/NexilisLite/Source/View/BNIView/BNIBookingWebView.swift

@@ -27,6 +27,8 @@ public class BNIBookingWebView: UIViewController, WKNavigationDelegate, UIScroll
     
     var indexImageVideoWv = 0
     var imageVideoPicker: ImageVideoPicker!
+    var blockedCertificate = ""
+    var allowedURLs = Set<String>()
     
     public override var preferredStatusBarStyle: UIStatusBarStyle {
         return .default
@@ -693,53 +695,115 @@ public class BNIBookingWebView: UIViewController, WKNavigationDelegate, UIScroll
         sender.endRefreshing()
     }
     
-//    public func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
-//        guard let serverTrust = challenge.protectionSpace.serverTrust else {
-//            completionHandler(.cancelAuthenticationChallenge, nil)
-//            return
-//        }
-//        if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0),
-//           let pinnedCertificateHash = getCertificateHash(from: serverCertificate),
-//           pinnedCertificateHash == Utils.getCertificatePinningWebview() {
-//            let credential = URLCredential(trust: serverTrust)
-//            completionHandler(.useCredential, credential) // Certificate matches, proceed
-//        } else {
-//            completionHandler(.cancelAuthenticationChallenge, nil) // Certificate doesn't match, cancel
-//        }
-//    }
-    
-    private func getCertificateHash(from certificate: SecCertificate) -> String? {
-        guard let publicKey = getPublicKey(from: certificate) else { return nil }
-        return hashPublicKey(publicKey)
+    public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping @MainActor (WKNavigationActionPolicy) -> Void) {
+        guard let url = navigationAction.request.url else {
+            decisionHandler(.cancel)
+            return
+        }
+        if allowedURLs.contains(url.absoluteString) {
+            print("✅ URL already allowed: \(url)")
+            decisionHandler(.allow)
+            return
+        }
+        validateSSLCertificate(url: url) { isValid in
+            print("is VALID? : \(isValid)")
+            if isValid {
+                self.allowedURLs.insert(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)
+                            }
+                        }
+                        self.allowedURLs.insert(url.absoluteString)
+                        decisionHandler(.allow)
+                    }
+                    let noAction = UIAlertAction(title: "No", style: .cancel) { _ in
+                        decisionHandler(.cancel)
+                    }
+                    alert.addAction(yesAction)
+                    alert.addAction(noAction)
+                    self.present(alert, animated: true, completion: nil)
+                }
+            }
+        }
     }
     
-    private func getPublicKey(from certificate: SecCertificate) -> Data? {
-        var trust: SecTrust?
-        let policy = SecPolicyCreateBasicX509()
-        let status = SecTrustCreateWithCertificates(certificate, policy, &trust)
-
-        guard status == errSecSuccess, let trust = trust else { return nil }
-
-        guard let publicKey = SecTrustCopyKey(trust) else { return nil }
+    private func validateSSLCertificate(url: URL, completion: @escaping (Bool) -> Void) {
+        let session = URLSession(configuration: .ephemeral, delegate: self, delegateQueue: nil)
+        let request = URLRequest(url: url)
+
+        let task = session.dataTask(with: request) { _, response, error in
+            if let error = error {
+                print("SSL Validation Error: \(error.localizedDescription)")
+                completion(false)
+                return
+            }
+            completion(true)
+        }
+        task.resume()
+    }
+}
 
-        var error: Unmanaged<CFError>?
-        if let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, &error) {
-            return publicKeyData as Data
+extension BNIBookingWebView: URLSessionDelegate {
+    public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
+        guard let serverTrust = challenge.protectionSpace.serverTrust else {
+            completionHandler(.cancelAuthenticationChallenge, nil)
+            return
+        }
+        if let publicKeyHash = extractPublicKeyHash(from: serverTrust) {
+            let domain = challenge.protectionSpace.host
+            let storedCertificate = Utils.getCertificatePinningWebview()
+            if let jsonData = storedCertificate.data(using: .utf8),
+               let certJson = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: String] {
+                if publicKeyHash == certJson[domain] {
+                    print("✅ Certificate Matched. Allowing Navigation.")
+                    completionHandler(.useCredential, URLCredential(trust: serverTrust))
+                } else {
+                    print("❌ Certificate Mismatch! Blocking Navigation.")
+                    blockedCertificate = publicKeyHash
+                    completionHandler(.cancelAuthenticationChallenge, nil)
+                }
+            }
         } else {
-            print("Error extracting public key: \(String(describing: error?.takeRetainedValue()))")
-            return nil
+            completionHandler(.cancelAuthenticationChallenge, nil)
         }
     }
-
-    private func hashPublicKey(_ publicKey: Data) -> String? {
-        // SHA-256 hash
+    
+    func extractPublicKeyHash(from serverTrust: SecTrust) -> String? {
+        guard let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else { return nil }
+        guard let publicKey = SecCertificateCopyKey(certificate) else { return nil }
+        
+        var error: Unmanaged<CFError>?
+        guard let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, &error) as Data? else {
+            print("❌ Failed to extract public key")
+            return nil
+        }
+        
+        // Compute SHA-256 hash
         var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
-        publicKey.withUnsafeBytes {
-            _ = CC_SHA256($0.baseAddress, CC_LONG(publicKey.count), &hash)
+        publicKeyData.withUnsafeBytes {
+            _ = CC_SHA256($0.baseAddress, CC_LONG(publicKeyData.count), &hash)
         }
-
-        // Base64 encode the hash
-        let base64Hash = Data(hash).base64EncodedString()
-        return "sha256/\(base64Hash)"
+        
+        let hashData = Data(hash)
+        let base64Hash = hashData.base64EncodedString()
+        
+        return base64Hash
     }
 }

+ 34 - 28
NexilisLite/NexilisLite/Source/View/Chat/EditorGroup.swift

@@ -1628,7 +1628,7 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
             let duration: CGFloat = info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat
             
             if self.constraintViewTextField.constant != keyboardHeight - 60 {
-                if self.contraintBottomMention.constant > 0 {
+                if self.contraintBottomMention.constant < self.contraintBottomMention.constant + keyboardHeight - 60 {
                     self.contraintBottomMention.constant = self.contraintBottomMention.constant + keyboardHeight - 60
                 }
                 self.constraintViewTextField.constant = keyboardHeight - 60
@@ -2269,9 +2269,9 @@ extension EditorGroup: ImageVideoPickerDelegate, PreviewAttachmentImageVideoDele
             let cancelButtonAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
             UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes , for: .normal)
         }
-        picker.dismiss(animated: true, completion: nil)
         guard let result = results.first else { return }
         if result.itemProvider.hasItemConformingToTypeIdentifier("com.compuserve.gif") {
+            picker.dismiss(animated: true, completion: nil)
             result.itemProvider.loadDataRepresentation(forTypeIdentifier: "com.compuserve.gif") { data, error in
                 if let error = error {
                     print("Error loading GIF: \(error.localizedDescription)")
@@ -2293,6 +2293,7 @@ extension EditorGroup: ImageVideoPickerDelegate, PreviewAttachmentImageVideoDele
                 }
             }
         } else if result.itemProvider.hasItemConformingToTypeIdentifier("public.image") {
+            picker.dismiss(animated: true, completion: nil)
             result.itemProvider.loadObject(ofClass: UIImage.self) { object, error in
                 if let image = object as? UIImage {
                     DispatchQueue.main.async {
@@ -2311,33 +2312,38 @@ extension EditorGroup: ImageVideoPickerDelegate, PreviewAttachmentImageVideoDele
                 }
             }
         } else if result.itemProvider.hasItemConformingToTypeIdentifier("public.movie") {
-            result.itemProvider.loadFileRepresentation(forTypeIdentifier: "public.movie") { tempURL, error in
-                if let tempURL = tempURL {
-                    let fileManager = FileManager.default
-                    let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
-                    let destinationURL = documentsDirectory.appendingPathComponent(tempURL.lastPathComponent)
-                    do {
-                        if fileManager.fileExists(atPath: destinationURL.path) {
-                            try fileManager.removeItem(at: destinationURL)
-                        }
-                        try fileManager.copyItem(at: tempURL, to: destinationURL)
-                        DispatchQueue.main.async {
-                            let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
-                            if (self.textFieldSend.textColor != .lightGray) {
-                                previewImageVC.currentTextTextField = self.textFieldSend.text
+            picker.dismiss(animated: true, completion: {
+                Nexilis.showLoader()
+                result.itemProvider.loadFileRepresentation(forTypeIdentifier: "public.movie") { tempURL, error in
+                    if let tempURL = tempURL {
+                        let fileManager = FileManager.default
+                        let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
+                        let destinationURL = documentsDirectory.appendingPathComponent(tempURL.lastPathComponent)
+                        do {
+                            if fileManager.fileExists(atPath: destinationURL.path) {
+                                try fileManager.removeItem(at: destinationURL)
+                            }
+                            try fileManager.copyItem(at: tempURL, to: destinationURL)
+                            DispatchQueue.main.async {
+                                Nexilis.hideLoader {
+                                    let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
+                                    if (self.textFieldSend.textColor != .lightGray) {
+                                        previewImageVC.currentTextTextField = self.textFieldSend.text
+                                    }
+                                    previewImageVC.modalPresentationStyle = .custom
+                                    previewImageVC.urlVideoPhpPicker = destinationURL
+                                    previewImageVC.delegate = self
+                                    previewImageVC.isAck = self.isAck
+                                    previewImageVC.isConfidential = self.isConfidential
+                                    self.present(previewImageVC, animated: true, completion: nil)
+                                }
                             }
-                            previewImageVC.modalPresentationStyle = .custom
-                            previewImageVC.urlVideoPhpPicker = destinationURL
-                            previewImageVC.delegate = self
-                            previewImageVC.isAck = self.isAck
-                            previewImageVC.isConfidential = self.isConfidential
-                            self.present(previewImageVC, animated: true, completion: nil)
+                        } catch {
+                            print("Error copying video file: \(error.localizedDescription)")
                         }
-                    } catch {
-                        print("Error copying video file: \(error.localizedDescription)")
                     }
                 }
-            }
+            })
         }
     }
     
@@ -2551,7 +2557,7 @@ extension EditorGroup: UITextViewDelegate {
     
     private func showMention(text: String) {
         if self.contraintBottomMention.constant < 0 {
-            self.contraintBottomMention.constant = 65 + ((self.keyboardHeightForMention != nil) ? self.keyboardHeightForMention! : 0)
+            self.contraintBottomMention.constant = 25 + ((self.keyboardHeightForMention != nil) ? self.keyboardHeightForMention! : 0) + self.heightTextFieldSend.constant
             if self.viewTextfield.subviews.contains(self.containerLink) {
                 self.contraintBottomMention.constant = self.contraintBottomMention.constant + 80
             }
@@ -2710,7 +2716,7 @@ extension EditorGroup: UITextViewDelegate {
             UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
                 self.constraintTopTextField.constant = self.constraintTopTextField.constant + 80
                 if self.contraintBottomMention.constant > 0 {
-                    self.contraintBottomMention.constant = self.contraintBottomMention.constant + 80
+                    self.contraintBottomMention.constant = self.contraintBottomMention.constant + 80 + self.heightTextFieldSend.constant
                 }
             }, completion: nil)
         }
@@ -6074,7 +6080,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
         UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
             self.constraintTopTextField.constant = self.constraintTopTextField.constant + 50
             if self.contraintBottomMention.constant > 0 {
-                self.contraintBottomMention.constant = self.contraintBottomMention.constant + 50
+                self.contraintBottomMention.constant = self.contraintBottomMention.constant + self.heightTextFieldSend.constant
             }
         }, completion: nil)
         if (self.currentIndexpath != nil) {

+ 31 - 25
NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift

@@ -3464,9 +3464,9 @@ extension EditorPersonal: PreviewAttachmentImageVideoDelegate, PHPickerViewContr
             let cancelButtonAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
             UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes , for: .normal)
         }
-        picker.dismiss(animated: true, completion: nil)
         guard let result = results.first else { return }
         if result.itemProvider.hasItemConformingToTypeIdentifier("com.compuserve.gif") {
+            picker.dismiss(animated: true, completion: nil)
             result.itemProvider.loadDataRepresentation(forTypeIdentifier: "com.compuserve.gif") { data, error in
                 if let error = error {
                     print("Error loading GIF: \(error.localizedDescription)")
@@ -3489,6 +3489,7 @@ extension EditorPersonal: PreviewAttachmentImageVideoDelegate, PHPickerViewContr
                 }
             }
         } else if result.itemProvider.hasItemConformingToTypeIdentifier("public.image") {
+            picker.dismiss(animated: true, completion: nil)
             result.itemProvider.loadObject(ofClass: UIImage.self) { object, error in
                 if let image = object as? UIImage {
                     DispatchQueue.main.async {
@@ -3508,34 +3509,39 @@ extension EditorPersonal: PreviewAttachmentImageVideoDelegate, PHPickerViewContr
                 }
             }
         } else if result.itemProvider.hasItemConformingToTypeIdentifier("public.movie") {
-            result.itemProvider.loadFileRepresentation(forTypeIdentifier: "public.movie") { tempURL, error in
-                if let tempURL = tempURL {
-                    let fileManager = FileManager.default
-                    let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
-                    let destinationURL = documentsDirectory.appendingPathComponent(tempURL.lastPathComponent)
-                    do {
-                        if fileManager.fileExists(atPath: destinationURL.path) {
-                            try fileManager.removeItem(at: destinationURL)
-                        }
-                        try fileManager.copyItem(at: tempURL, to: destinationURL)
-                        DispatchQueue.main.async {
-                            let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
-                            if (self.textFieldSend.textColor != .lightGray) {
-                                previewImageVC.currentTextTextField = self.textFieldSend.text
+            picker.dismiss(animated: true, completion: {
+                Nexilis.showLoader()
+                result.itemProvider.loadFileRepresentation(forTypeIdentifier: "public.movie") { tempURL, error in
+                    if let tempURL = tempURL {
+                        let fileManager = FileManager.default
+                        let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
+                        let destinationURL = documentsDirectory.appendingPathComponent(tempURL.lastPathComponent)
+                        do {
+                            if fileManager.fileExists(atPath: destinationURL.path) {
+                                try fileManager.removeItem(at: destinationURL)
+                            }
+                            try fileManager.copyItem(at: tempURL, to: destinationURL)
+                            DispatchQueue.main.async {
+                                Nexilis.hideLoader {
+                                    let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
+                                    if (self.textFieldSend.textColor != .lightGray) {
+                                        previewImageVC.currentTextTextField = self.textFieldSend.text
+                                    }
+                                    previewImageVC.modalPresentationStyle = .custom
+                                    previewImageVC.urlVideoPhpPicker = destinationURL
+                                    previewImageVC.delegate = self
+                                    previewImageVC.isAck = self.isAck
+                                    previewImageVC.isConfidential = self.isConfidential
+                                    previewImageVC.isCC = self.isContactCenter
+                                    self.present(previewImageVC, animated: true, completion: nil)
+                                }
                             }
-                            previewImageVC.modalPresentationStyle = .custom
-                            previewImageVC.urlVideoPhpPicker = destinationURL
-                            previewImageVC.delegate = self
-                            previewImageVC.isAck = self.isAck
-                            previewImageVC.isConfidential = self.isConfidential
-                            previewImageVC.isCC = self.isContactCenter
-                            self.present(previewImageVC, animated: true, completion: nil)
+                        } catch {
+                            print("Error copying video file: \(error.localizedDescription)")
                         }
-                    } catch {
-                        print("Error copying video file: \(error.localizedDescription)")
                     }
                 }
-            }
+            })
         }
     }
     

+ 108 - 89
NexilisLite/NexilisLite/Source/View/Chat/PreviewAttachmentImageVideo.swift

@@ -357,107 +357,125 @@ class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITex
                 delegate!.sendChatFromPreviewImage(message_text: textFieldSend.text!, attachment_flag: "1", image_id: compressedImageName, video_id: "", thumb_id: thumbName, gif_id: "", viewController: self)
             }
         } else {
-            var dataVideo: Data?
-            if imageVideoData != nil || urlVideoPhpPicker != nil {
-                if imageVideoData != nil {
-                    dataVideo = try? Data(contentsOf: imageVideoData![.mediaURL] as! URL)
-                } else {
-                    dataVideo = try? Data(contentsOf: urlVideoPhpPicker!)
+            Nexilis.showLoader()
+            DispatchQueue.global().async { [self] in
+                var dataVideo: Data?
+                if imageVideoData != nil || urlVideoPhpPicker != nil {
+                    if imageVideoData != nil {
+                        dataVideo = try? Data(contentsOf: imageVideoData![.mediaURL] as! URL)
+                    } else {
+                        dataVideo = try? Data(contentsOf: urlVideoPhpPicker!)
+                    }
                 }
-            }
-            if var dataVideo = dataVideo {
-                let sizeOfVideo = Double(dataVideo.count / 1048576)
-                if (sizeOfVideo > 10.0) {
-                    let compressedURL = NSURL.fileURL(withPath: NSTemporaryDirectory() + UUID().uuidString + ".mp4")
-                    compressVideo(inputURL: imageVideoData![.mediaURL] as! URL,
-                                  outputURL: compressedURL) { exportSession in
-                        guard let session = exportSession else {
-                            return
-                        }
-                        
-                        switch session.status {
-                        case .unknown:
-                            break
-                        case .waiting:
-                            break
-                        case .exporting:
-                            break
-                        case .completed:
-                            guard let compressedData = try? Data(contentsOf: compressedURL) else {
+                if let dataVideotoCompress = dataVideo {
+                    let sizeInKB = Double(dataVideotoCompress.count) / 1024.0
+                    let sizeOfVideo = sizeInKB / 1024.0
+                    if (sizeOfVideo > 10.0) {
+                        Nexilis.dispatch = DispatchGroup()
+                        Nexilis.dispatch?.enter()
+                        let compressedURL = NSURL.fileURL(withPath: NSTemporaryDirectory() + UUID().uuidString + ".mp4")
+                        compressVideo(inputURL: (imageVideoData != nil ? imageVideoData![.mediaURL] as? URL : urlVideoPhpPicker)!,
+                                      outputURL: compressedURL) { exportSession in
+                            guard let session = exportSession else {
+                                if let dispatch = Nexilis.dispatch {
+                                    dispatch.leave()
+                                }
                                 return
                             }
-                            dataVideo = compressedData
-                        case .failed:
-                            break
-                        case .cancelled:
-                            break
-                        @unknown default:
-                            break
+                            
+                            switch session.status {
+                            case .unknown:
+                                break
+                            case .waiting:
+                                break
+                            case .exporting:
+                                break
+                            case .completed:
+                                guard let compressedData = try? Data(contentsOf: compressedURL) else {
+                                    return
+                                }
+                                dataVideo = compressedData
+                                if let dispatch = Nexilis.dispatch {
+                                    dispatch.leave()
+                                }
+                            case .failed:
+                                break
+                            case .cancelled:
+                                break
+                            @unknown default:
+                                break
+                            }
                         }
                     }
                 }
-            }
-            let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
-            var urlVideo = ""
-            var originalVideoName = ""
-            var renamedVideoName = ""
-            var thumbName = ""
-            if (fromCopy && dataGIF != nil) {
-                originalVideoName = "\(Date().currentTimeMillis())_copyGif"
-                renamedVideoName = "Nexilis_gif_\(originalVideoName)"
-                thumbName = "THUMB_Nexilis_gif_\(originalVideoName)"
-            } else {
-                if imageVideoData != nil {
-                    urlVideo = (imageVideoData![.mediaURL] as! NSURL).absoluteString!
-                } else {
-                    urlVideo = (urlVideoPhpPicker! as NSURL).absoluteString!
-                }
-                originalVideoName = (urlVideo as NSString).lastPathComponent
-                renamedVideoName = "Nexilis_video_\(originalVideoName)"
-                thumbName = "THUMB_Nexilis_video_\(originalVideoName)"
-            }
-            let fileURL = documentsDirectory.appendingPathComponent(renamedVideoName)
-            if !FileManager.default.fileExists(atPath: fileURL.path) {
-                do {
-                    if let dataVideo = dataVideo {
-                        try dataVideo.write(to: fileURL)
-                    } else if let dataGIF = dataGIF {
-                        try dataGIF.write(to: fileURL)
-                    }
-                    //print("file saved")
-                } catch {
-                    //print("error saving file:", error)
-                }
-            }
-            var dataThumbVideo: Data?
-            if !fromCopy {
-                dataThumbVideo = imagePreview.image!.jpegData(compressionQuality:  1.0)
-            }
-            let fileURLTHUMB = documentsDirectory.appendingPathComponent(thumbName)
-            if !FileManager.default.fileExists(atPath: fileURLTHUMB.path) {
-                do {
-                    if let dataThumbVideo = dataThumbVideo {
-                        try dataThumbVideo.write(to: fileURLTHUMB)
-                    } else {
-                        if let dataGIF = dataGIF {
-                            if let dataThumbGif = UIImage(data: dataGIF) {
-                                if let compressedDataThumbGif = dataThumbGif.jpegData(compressionQuality: 1.0) {
-                                    try compressedDataThumbGif.write(to: fileURLTHUMB)
+                Nexilis.dispatch?.wait()
+                Nexilis.dispatch = nil
+                DispatchQueue.main.async {
+                    Nexilis.hideLoader { [self] in
+                        let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
+                        var urlVideo = ""
+                        var originalVideoName = ""
+                        var renamedVideoName = ""
+                        var thumbName = ""
+                        if (fromCopy && dataGIF != nil) {
+                            originalVideoName = "\(Date().currentTimeMillis())_copyGif"
+                            renamedVideoName = "Nexilis_gif_\(originalVideoName)"
+                            thumbName = "THUMB_Nexilis_gif_\(originalVideoName)"
+                        } else {
+                            if imageVideoData != nil {
+                                urlVideo = (imageVideoData![.mediaURL] as! NSURL).absoluteString!
+                            } else {
+                                urlVideo = (urlVideoPhpPicker! as NSURL).absoluteString!
+                            }
+                            originalVideoName = (urlVideo as NSString).lastPathComponent
+                            renamedVideoName = "Nexilis_video_\(originalVideoName)"
+                            thumbName = "THUMB_Nexilis_video_\(originalVideoName)"
+                        }
+                        let fileURL = documentsDirectory.appendingPathComponent(renamedVideoName)
+                        if !FileManager.default.fileExists(atPath: fileURL.path) {
+                            do {
+                                if let dataVideo = dataVideo {
+                                    try dataVideo.write(to: fileURL)
+                                } else if let dataGIF = dataGIF {
+                                    try dataGIF.write(to: fileURL)
                                 }
+                                //print("file saved")
+                            } catch {
+                                //print("error saving file:", error)
                             }
                         }
+                        var dataThumbVideo: Data?
+                        if !fromCopy {
+                            dataThumbVideo = imagePreview.image!.jpegData(compressionQuality:  1.0)
+                        }
+                        let fileURLTHUMB = documentsDirectory.appendingPathComponent(thumbName)
+                        if !FileManager.default.fileExists(atPath: fileURLTHUMB.path) {
+                            do {
+                                if let dataThumbVideo = dataThumbVideo {
+                                    try dataThumbVideo.write(to: fileURLTHUMB)
+                                } else {
+                                    if let dataGIF = dataGIF {
+                                        if let dataThumbGif = UIImage(data: dataGIF) {
+                                            if let compressedDataThumbGif = dataThumbGif.jpegData(compressionQuality: 1.0) {
+                                                try compressedDataThumbGif.write(to: fileURLTHUMB)
+                                            }
+                                        }
+                                    }
+                                }
+                                //print("thumb saved")
+                            } catch {
+                                //print("error saving file:", error)
+                            }
+                        }
+                        self.dismiss(animated: true, completion: nil)
+                        if (textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines) == "Send message".localized() && textFieldSend.textColor == UIColor.lightGray) {
+                            delegate!.sendChatFromPreviewImage(message_text: "", attachment_flag: "2", image_id: "", video_id: renamedVideoName, thumb_id: thumbName, gif_id: dataGIF != nil ? renamedVideoName : "", viewController: self)
+                        } else {
+                            delegate!.sendChatFromPreviewImage(message_text: textFieldSend.text!, attachment_flag: "2", image_id: "", video_id: renamedVideoName, thumb_id: thumbName, gif_id: dataGIF != nil ? renamedVideoName : "", viewController: self)
+                        }
                     }
-                    //print("thumb saved")
-                } catch {
-                    //print("error saving file:", error)
                 }
             }
-            self.dismiss(animated: true, completion: nil)
-            if (textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines) == "Send message".localized() && textFieldSend.textColor == UIColor.lightGray) {
-                delegate!.sendChatFromPreviewImage(message_text: "", attachment_flag: "2", image_id: "", video_id: renamedVideoName, thumb_id: thumbName, gif_id: dataGIF != nil ? renamedVideoName : "", viewController: self)
-            } else {
-                delegate!.sendChatFromPreviewImage(message_text: textFieldSend.text!, attachment_flag: "2", image_id: "", video_id: renamedVideoName, thumb_id: thumbName, gif_id: dataGIF != nil ? renamedVideoName : "", viewController: self)
-            }
         }
     }
     
@@ -474,6 +492,7 @@ class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITex
         
         exportSession.outputURL = outputURL
         exportSession.outputFileType = .mp4
+        exportSession.shouldOptimizeForNetworkUse = true
         exportSession.exportAsynchronously {
             handler(exportSession)
         }

+ 46 - 15
NexilisLite/NexilisLite/Source/View/Control/NotificationSound.swift

@@ -14,6 +14,8 @@ public class NotificationSound: UIViewController, UITableViewDelegate, UITableVi
     let tableView = UITableView()
     var data: [NotifSound] = []
     var isSelectedSound = 0
+    var lastSelectedSound = 0
+    var audioPlayer: AVAudioPlayer?
 
     public override func viewDidLoad() {
         super.viewDidLoad()
@@ -45,15 +47,22 @@ public class NotificationSound: UIViewController, UITableViewDelegate, UITableVi
         navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(cancel(sender:)))
         
         navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Save".localized(), style: .plain, target: self, action: #selector(save(sender:)))
-        data.append(NotifSound(id: 1007, name: "sms-received1 (Default)", isSelected: false))
-        data.append(NotifSound(id: 1008, name: "sms-received2", isSelected: false))
-        data.append(NotifSound(id: 1009, name: "sms-received3", isSelected: false))
-        data.append(NotifSound(id: 1010, name: "sms-received4", isSelected: false))
-        data.append(NotifSound(id: 1013, name: "sms-received5", isSelected: false))
-        data.append(NotifSound(id: 1014, name: "sms-received6", isSelected: false))
-        var selectedSound: String = SecureUserDefaults.shared.value(forKey: "notifSoundPersonal") ?? ""
+        data.append(NotifSound(id: 001, name: "Nexilis Message (Default)", isSelected: false))
+        data.append(NotifSound(id: 002, name: "Dutifully", isSelected: false))
+        data.append(NotifSound(id: 003, name: "Elegant", isSelected: false))
+        data.append(NotifSound(id: 004, name: "Eventually", isSelected: false))
+        data.append(NotifSound(id: 005, name: "Hangover", isSelected: false))
+        data.append(NotifSound(id: 006, name: "Juntos", isSelected: false))
+        data.append(NotifSound(id: 007, name: "Light Hearted", isSelected: false))
+        data.append(NotifSound(id: 008, name: "Magic", isSelected: false))
+        data.append(NotifSound(id: 009, name: "Out of Nowhere", isSelected: false))
+        data.append(NotifSound(id: 010, name: "Relax", isSelected: false))
+        data.append(NotifSound(id: 011, name: "Strong Minded", isSelected: false))
+        data.append(NotifSound(id: 012, name: "Swift gesture", isSelected: false))
+        data.append(NotifSound(id: 013, name: "Upset", isSelected: false))
+        var selectedSound: String = SecureUserDefaults.shared.value(forKey: "newNotifSoundPersonal") ?? ""
         if !isPersonal {
-            selectedSound = SecureUserDefaults.shared.value(forKey: "notifSoundGroup") ?? ""
+            selectedSound = SecureUserDefaults.shared.value(forKey: "newNotifSoundGroup") ?? ""
         }
         if !selectedSound.isEmpty {
             let selectedSoundId = Int(selectedSound.components(separatedBy: ":")[0])
@@ -63,11 +72,11 @@ public class NotificationSound: UIViewController, UITableViewDelegate, UITableVi
                 isSelectedSound = selectedSoundId!
             } else {
                 data[0].isSelected = true
-                isSelectedSound = 1007
+                isSelectedSound = 001
             }
         } else {
             data[0].isSelected = true
-            isSelectedSound = 1007
+            isSelectedSound = 001
         }
     }
     
@@ -78,9 +87,13 @@ public class NotificationSound: UIViewController, UITableViewDelegate, UITableVi
     @objc func save(sender: Any) {
         let idx = data.firstIndex(where: {$0.id == isSelectedSound})
         if isPersonal {
-            SecureUserDefaults.shared.set("\(data[idx!].id):\(data[idx!].name)", forKey: "notifSoundPersonal")
+            SecureUserDefaults.shared.set("\(data[idx!].id):\(data[idx!].name)", forKey: "newNotifSoundPersonal")
         } else {
-            SecureUserDefaults.shared.set("\(data[idx!].id):\(data[idx!].name)", forKey: "notifSoundGroup")
+            SecureUserDefaults.shared.set("\(data[idx!].id):\(data[idx!].name)", forKey: "newNotifSoundGroup")
+        }
+        //stopSound
+        if audioPlayer != nil && audioPlayer!.isPlaying {
+            audioPlayer?.stop()
         }
         navigationController?.dismiss(animated: true, completion: nil)
     }
@@ -93,10 +106,28 @@ public class NotificationSound: UIViewController, UITableViewDelegate, UITableVi
         
         let idxNew = data.firstIndex(where: {$0.id == data[indexPath.row].id})
         data[idxNew!].isSelected = true
+        if lastSelectedSound != 0 {
+            //stopSound
+            audioPlayer?.stop()
+        }
+        lastSelectedSound = data[indexPath.row].id
         isSelectedSound = data[indexPath.row].id
-        
-        let systemSoundID: SystemSoundID = SystemSoundID(isSelectedSound)
-        AudioServicesPlaySystemSound(systemSoundID)
+        //playSound
+        var nameSound = data[indexPath.row].name.replacingOccurrences(of: " ", with: "_")
+        if nameSound.contains("_(Default)") {
+            nameSound = nameSound.replacingOccurrences(of: "_(Default)", with: "")
+        }
+        var soundURL = Bundle.resourceBundle(for: Nexilis.self).url(forResource: nameSound, withExtension: "mp3")
+        if soundURL == nil {
+            soundURL = Bundle.resourcesMediaBundle(for: Nexilis.self).url(forResource: nameSound, withExtension: "mp3")
+        }
+        do {
+            audioPlayer = try AVAudioPlayer(contentsOf: soundURL!)
+            audioPlayer?.prepareToPlay()
+            audioPlayer?.play()
+        } catch {
+            
+        }
         tableView.reloadData()
     }
 

+ 1 - 1
NexilisLite/Podfile

@@ -7,7 +7,7 @@ target 'NexilisLite' do
 
   # Pods for NexilisLite
 
-  pod 'nuSDKService', '~> 4.0.2'
+  pod 'nuSDKService', '~> 4.0.3'
   pod 'FMDB', '~> 2.7.12'
   pod 'NotificationBannerSwift', '3.1.0'
   pod 'Alamofire', '~> 5.10.1'