Преглед изворни кода

update code and after release for 5.0.52

alqindiirsyam пре 2 недеља
родитељ
комит
5eb3d8fac1
33 измењених фајлова са 1773 додато и 385 уклоњено
  1. BIN
      .DS_Store
  2. BIN
      AppBuilder/.DS_Store
  3. 4 4
      AppBuilder/AppBuilder.xcodeproj/project.pbxproj
  4. 2 0
      AppBuilder/AppBuilder/AppBuilder.entitlements
  5. 1 0
      AppBuilder/AppBuilder/AppDelegate.swift
  6. BIN
      AppBuilder/AppBuilder/Assets.xcassets/.DS_Store
  7. 21 0
      AppBuilder/AppBuilder/Assets.xcassets/indicator_default_tab.imageset/Contents.json
  8. BIN
      AppBuilder/AppBuilder/Assets.xcassets/indicator_default_tab.imageset/indicator_default_tab.png
  9. 3 0
      AppBuilder/AppBuilder/FirstTabViewController.swift
  10. 33 25
      AppBuilder/AppBuilder/FourthTabViewController.swift
  11. 15 1
      AppBuilder/AppBuilder/SecondTabViewController.swift
  12. 3 0
      AppBuilder/AppBuilder/ThirdTabViewController.swift
  13. 21 2
      AppBuilder/AppBuilder/ViewController.swift
  14. BIN
      NexilisLite/.DS_Store
  15. 8 0
      NexilisLite/NexilisLite/Source/APIS.swift
  16. 58 0
      NexilisLite/NexilisLite/Source/CoreMessage_TMessageBank.swift
  17. 17 0
      NexilisLite/NexilisLite/Source/CoreMessage_TMessageCode.swift
  18. 8 0
      NexilisLite/NexilisLite/Source/CoreMessage_TMessageKey.swift
  19. 131 63
      NexilisLite/NexilisLite/Source/Database.swift
  20. 497 0
      NexilisLite/NexilisLite/Source/Extension.swift
  21. 57 34
      NexilisLite/NexilisLite/Source/MasterKeyUtil.swift
  22. 10 5
      NexilisLite/NexilisLite/Source/Model/User.swift
  23. 29 13
      NexilisLite/NexilisLite/Source/Nexilis.swift
  24. 8 0
      NexilisLite/NexilisLite/Source/Utils.swift
  25. 6 0
      NexilisLite/NexilisLite/Source/View/BNIView/BNIBookingWebView.swift
  26. 20 20
      NexilisLite/NexilisLite/Source/View/Chat/ChatGPTBotView.swift
  27. 110 5
      NexilisLite/NexilisLite/Source/View/Chat/CommunityNew.swift
  28. 91 67
      NexilisLite/NexilisLite/Source/View/Chat/EditorGroup.swift
  29. 91 67
      NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift
  30. 28 23
      NexilisLite/NexilisLite/Source/View/Chat/PreviewAttachmentImageVideo.swift
  31. 10 0
      NexilisLite/NexilisLite/Source/View/Chat/SecureFolderView.swift
  32. 8 16
      NexilisLite/NexilisLite/Source/View/Control/SettingTableViewController.swift
  33. 483 40
      StreamShield/StreamShield/Source/SecurityShield.swift

BIN
AppBuilder/.DS_Store


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

@@ -568,7 +568,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 5.0.51;
+				MARKETING_VERSION = 5.0.52;
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
@@ -604,7 +604,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 5.0.51;
+				MARKETING_VERSION = 5.0.52;
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
@@ -640,7 +640,7 @@
 					"@executable_path/../../Frameworks",
 				);
 				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
-				MARKETING_VERSION = 5.0.51;
+				MARKETING_VERSION = 5.0.52;
 				OTHER_CFLAGS = "-fstack-protector-strong";
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder.AppBuilderShare;
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -679,7 +679,7 @@
 					"@executable_path/../../Frameworks",
 				);
 				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
-				MARKETING_VERSION = 5.0.51;
+				MARKETING_VERSION = 5.0.52;
 				OTHER_CFLAGS = "-fstack-protector-strong";
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder.AppBuilderShare;
 				PRODUCT_NAME = "$(TARGET_NAME)";

+ 2 - 0
AppBuilder/AppBuilder/AppBuilder.entitlements

@@ -6,6 +6,8 @@
 	<string>development</string>
 	<key>com.apple.developer.calling-app</key>
 	<true/>
+	<key>com.apple.developer.networking.wifi-info</key>
+	<true/>
 	<key>com.apple.security.application-groups</key>
 	<array>
 		<string>group.nexilis.share</string>

+ 1 - 0
AppBuilder/AppBuilder/AppDelegate.swift

@@ -26,6 +26,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, PKPushRegistryDelegate {
         registerForPushNotifications()
         registerForVoIPPushNotifications()
         FirebaseApp.configure()
+        APIS.monitoredActivity()
         return true
     }
 

BIN
AppBuilder/AppBuilder/Assets.xcassets/.DS_Store


+ 21 - 0
AppBuilder/AppBuilder/Assets.xcassets/indicator_default_tab.imageset/Contents.json

@@ -0,0 +1,21 @@
+{
+  "images" : [
+    {
+      "filename" : "indicator_default_tab.png",
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
AppBuilder/AppBuilder/Assets.xcassets/indicator_default_tab.imageset/indicator_default_tab.png


+ 3 - 0
AppBuilder/AppBuilder/FirstTabViewController.swift

@@ -218,6 +218,9 @@ class FirstTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
                         ViewController.middleButton.isHidden = false
                         ViewController.alwaysHideButton = false
                     }
+                    if ViewController.middleButton.isHidden && PrefsUtil.getCpaasMode() == PrefsUtil.CPAAS_MODE_DOCKED {
+                        viewController.showMiddleButtonAfterPush()
+                    }
                 }
             } else if PrefsUtil.getCpaasMode() != PrefsUtil.CPAAS_MODE_DOCKED {
                 DispatchQueue.main.async {

+ 33 - 25
AppBuilder/AppBuilder/FourthTabViewController.swift

@@ -116,15 +116,17 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
                     if viewController.tabBar.isHidden {
                         viewController.tabBar.isHidden = false
                         ViewController.alwaysHideButton = false
-                        ViewController.middleButton.isHidden = false
+                    }
+                    if ViewController.middleButton.isHidden && PrefsUtil.getCpaasMode() == PrefsUtil.CPAAS_MODE_DOCKED {
+                        viewController.showMiddleButtonAfterPush()
                     }
                 }
             } 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
+                            viewController.tabBar.isHidden = false
                         }
                     }
                 }
@@ -262,31 +264,15 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
             })
         }
         
-        Item.menus["Config"] = [
-            Item(icon: UIImage(systemName: "iphone"), title: "Create Your Own App".localized()),
-            Item(icon: UIImage(systemName: "gearshape.circle"), title: "Configure Floating Button".localized()),
-            Item(icon: UIImage(systemName: "paintbrush"), title: "Switch Style".localized())
-        ]
-        if !isChangeProfile || !Nexilis.checkingAccess(key: "create_miniapp") {
-            if Item.menus["Config"]!.count > 1 {
-                Item.menus["Config"]!.remove(at: 0)
-            } else {
-                Item.menus["Config"]!.removeAll()
-            }
+        Item.menus["Config"] = []
+        if isChangeProfile && Nexilis.checkingAccess(key: "create_miniapp") {
+            Item.menus["Config"]?.append(Item(icon: UIImage(systemName: "iphone"), title: "Create Your Own App".localized()))
         }
-        if !Nexilis.showButtonFB || !Nexilis.checkingAccess(key: "can_config_fb") {
-            if Item.menus["Config"]!.count > 1 {
-                Item.menus["Config"]!.remove(at: 1)
-            } else {
-                Item.menus["Config"]!.removeAll()
-            }
+        if Nexilis.showButtonFB && Nexilis.checkingAccess(key: "can_config_fb") {
+            Item.menus["Config"]?.append(Item(icon: UIImage(systemName: "gearshape.circle"), title: "Configure Floating Button".localized()))
         }
-        if !isChangeProfile || !Nexilis.checkingAccess(key: "can_switch_style") {
-            if Item.menus["Config"]!.count > 1 {
-                Item.menus["Config"]!.remove(at: 1)
-            } else {
-                Item.menus["Config"]!.removeAll()
-            }
+        if isChangeProfile && Nexilis.checkingAccess(key: "can_switch_style") {
+            Item.menus["Config"]?.append(Item(icon: UIImage(systemName: "paintbrush"), title: "Switch Style".localized()))
         }
         if Utils.getIsLoadThemeFromOther() {
             Item.menus["Config"]?.insert(Item(icon: UIImage(systemName: "iphone"), title: "Back to Company App".localized()), at: 1)
@@ -627,6 +613,9 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
                     NotificationCenter.default.post(name: NSNotification.Name(rawValue: "imageFBUpdate"), object: nil, userInfo: dataImage)
                     self.makeMenu()
                 }
+                controller.hidesBottomBarWhenPushed = true
+                ViewController.hideDockedButton()
+                ViewController.removeMiddleButton()
                 navigationController?.show(controller, sender: nil)
             }
         } else if item.title == "Access Admin / Internal Features".localized() || item.title == "Change Admin / Internal Password".localized() {
@@ -733,9 +722,15 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
             APIS.openChatWallpaper()
         } else if item.title == "Sign-In".localized() {
             guard let controller = APIS.getControllerSign(forceSignIn: true) else { return }
+            controller.hidesBottomBarWhenPushed = true
+            ViewController.hideDockedButton()
+            ViewController.removeMiddleButton()
             navigationController?.show(controller, sender: nil)
         } else if item.title == "Sign-Up/Sign-In".localized() {
             guard let controller = APIS.getControllerSign() else { return }
+            controller.hidesBottomBarWhenPushed = true
+            ViewController.hideDockedButton()
+            ViewController.removeMiddleButton()
             navigationController?.show(controller, sender: nil)
         } else if item.title == "Sign-Out".localized() {
             let alert = LibAlertController(title: "Sign-Out".localized(), message: "Are you sure want to logout?".localized(), preferredStyle: .alert)
@@ -889,13 +884,22 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
             self.present(navigationController, animated: true)
         } else if item.title == "Backup & Restore".localized() {
             let controller = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "backupRestore") as! BackupRestoreView
+            controller.hidesBottomBarWhenPushed = true
+            ViewController.hideDockedButton()
+            ViewController.removeMiddleButton()
             navigationController?.show(controller, sender: nil)
         } else if item.title == "Set Internal Account".localized() {
             let controller = SetInternalCSAccount()
+            controller.hidesBottomBarWhenPushed = true
+            ViewController.hideDockedButton()
+            ViewController.removeMiddleButton()
             navigationController?.show(controller, sender: nil)
         } else if item.title == "Set CS Account".localized() {
             let controller = SetInternalCSAccount()
             controller.isSetCS = true
+            controller.hidesBottomBarWhenPushed = true
+            ViewController.hideDockedButton()
+            ViewController.removeMiddleButton()
             navigationController?.show(controller, sender: nil)
         } else if item.title == "Configure Floating Button".localized() {
             let viewConfigureFB = ConfigureFloatingButton()
@@ -904,6 +908,9 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
             self.present(viewConfigureFB, animated: true)
         } else if item.title == "Validation Transaction Limit".localized() {
             let controller = ValidationTransactionLimit()
+            controller.hidesBottomBarWhenPushed = true
+            ViewController.hideDockedButton()
+            ViewController.removeMiddleButton()
             navigationController?.show(controller, sender: nil)
         } else if item.title == "Create Your Own App".localized() {
             let controller = BNIBookingWebView()
@@ -930,6 +937,7 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
                                 Utils.resetValueSuperApp()
                                 Utils.setValueInitialApp(data: Utils.getPrefTheme())
                                 Utils.setLastTabSelected(value: 0)
+                                Utils.setIsWATheme(value: false)
                                 UIApplication.shared.setAlternateIconName(nil)
                                 Database.shared.database?.inTransaction({ fmdb, rollback in
                                     _ = Database.shared.deleteRecord(fmdb: fmdb, table: "GROUPZ", _where: "")

+ 15 - 1
AppBuilder/AppBuilder/SecondTabViewController.swift

@@ -578,7 +578,9 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
                     if viewController.tabBar.isHidden {
                         viewController.tabBar.isHidden = false
                         ViewController.alwaysHideButton = false
-                        ViewController.middleButton.isHidden = false
+                    }
+                    if ViewController.middleButton.isHidden && PrefsUtil.getCpaasMode() == PrefsUtil.CPAAS_MODE_DOCKED {
+                        viewController.showMiddleButtonAfterPush()
                     }
                 }
             } else if PrefsUtil.getCpaasMode() != PrefsUtil.CPAAS_MODE_DOCKED {
@@ -1191,6 +1193,8 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
                 let controller = ArchivedChatView()
                 controller.archivedChats = archivedChats
                 controller.hidesBottomBarWhenPushed = true
+                ViewController.hideDockedButton()
+                ViewController.removeMiddleButton()
                 navigationController?.show(controller, sender: nil)
                 return
             }
@@ -1210,6 +1214,8 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
                 let smartChatVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "chatGptVC") as! ChatGPTBotView
                 smartChatVC.hidesBottomBarWhenPushed = true
                 smartChatVC.fromNotification = false
+                ViewController.hideDockedButton()
+                ViewController.removeMiddleButton()
                 navigationController?.show(smartChatVC, sender: nil)
             } else if data.messageScope == MessageScope.WHISPER || data.messageScope == MessageScope.CALL || data.messageScope == MessageScope.MISSED_CALL {
                 if data.pin.isEmpty {
@@ -1218,6 +1224,8 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
                 let editorPersonalVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorPersonalVC") as! EditorPersonal
                 editorPersonalVC.hidesBottomBarWhenPushed = true
                 editorPersonalVC.unique_l_pin = data.pin
+                ViewController.hideDockedButton()
+                ViewController.removeMiddleButton()
                 navigationController?.show(editorPersonalVC, sender: nil)
             } else {
                 if data.pin.isEmpty {
@@ -1226,6 +1234,8 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
                 let editorGroupVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorGroupVC") as! EditorGroup
                 editorGroupVC.hidesBottomBarWhenPushed = true
                 editorGroupVC.unique_l_pin = data.pin
+                ViewController.hideDockedButton()
+                ViewController.removeMiddleButton()
                 navigationController?.show(editorGroupVC, sender: nil)
             }
         case 1:
@@ -1354,6 +1364,8 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
                             let editorGroupVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorGroupVC") as! EditorGroup
                             editorGroupVC.hidesBottomBarWhenPushed = true
                             editorGroupVC.unique_l_pin = group.id
+                            ViewController.hideDockedButton()
+                            ViewController.removeMiddleButton()
                             self.navigationController?.show(editorGroupVC, sender: nil)
                         }
                     }
@@ -1376,6 +1388,8 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
                     let editorGroupVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorGroupVC") as! EditorGroup
                     editorGroupVC.hidesBottomBarWhenPushed = true
                     editorGroupVC.unique_l_pin = groupId
+                    ViewController.hideDockedButton()
+                    ViewController.removeMiddleButton()
                     navigationController?.show(editorGroupVC, sender: nil)
                     cursorMember.close()
                 } else {

+ 3 - 0
AppBuilder/AppBuilder/ThirdTabViewController.swift

@@ -221,6 +221,9 @@ class ThirdTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
                         ViewController.middleButton.isHidden = false
                         ViewController.alwaysHideButton = false
                     }
+                    if ViewController.middleButton.isHidden && PrefsUtil.getCpaasMode() == PrefsUtil.CPAAS_MODE_DOCKED {
+                        viewController.showMiddleButtonAfterPush()
+                    }
                 }
             } else if PrefsUtil.getCpaasMode() != PrefsUtil.CPAAS_MODE_DOCKED {
                 DispatchQueue.main.async {

+ 21 - 2
AppBuilder/AppBuilder/ViewController.swift

@@ -63,6 +63,7 @@ class ViewController: UITabBarController, UITabBarControllerDelegate, SettingMAB
     var indicatorView : UIView!
     var indicatorImage : UIImageView!
     var imageIndicator: UIImage!
+    var isDefaultIndicator = false
     
     public static var def: ViewController?
 
@@ -101,7 +102,7 @@ class ViewController: UITabBarController, UITabBarControllerDelegate, SettingMAB
         callTab = UINavigationController(rootViewController: CallLogVC())
         chatWATab = UINavigationController(rootViewController: ChatWALikeVC())
         communityTab = UINavigationController(rootViewController: CommunityList())
-        secureFolderTab = UINavigationController(rootViewController: SecureFolderViewController())
+        secureFolderTab = UINavigationController(rootViewController: SecureFolderViewController(isTab: true))
         statusUpdateTab = UINavigationController(rootViewController: StatusUpdateVC())
         
         self.delegate = self
@@ -291,6 +292,10 @@ class ViewController: UITabBarController, UITabBarControllerDelegate, SettingMAB
                     }
                 }
             }
+        } else {
+            isDefaultIndicator = true
+            imageIndicator = UIImage(named: "indicator_default_tab")!
+            addCustomViewAboveTabBarItem(at: Utils.getLastTabSelected(), image: imageIndicator)
         }
         DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: { [self] in
             checkCounter()
@@ -320,7 +325,11 @@ class ViewController: UITabBarController, UITabBarControllerDelegate, SettingMAB
         if image != nil {
             indicatorImage = UIImageView(image: image)
             indicatorImage.contentMode = .scaleAspectFit
-            indicatorImage.frame = CGRect(x: xCenter, y: 5, width: 40, height: 40)
+            if isDefaultIndicator {
+                indicatorImage.frame = CGRect(x: xCenter - 10, y: -5, width: 60, height: 60)
+            } else {
+                indicatorImage.frame = CGRect(x: xCenter, y: 5, width: 40, height: 40)
+            }
             tabBar.addSubview(indicatorImage)
             tabBar.sendSubviewToBack(indicatorImage)
         }
@@ -1453,6 +1462,16 @@ class ViewController: UITabBarController, UITabBarControllerDelegate, SettingMAB
         }
     }
     
+    public func showMiddleButtonAfterPush() {
+        self.view.bringSubviewToFront(ViewController.chatButton)
+        self.view.bringSubviewToFront(ViewController.callButton)
+        self.view.bringSubviewToFront(ViewController.ccButton)
+        self.view.bringSubviewToFront(ViewController.streamingButton)
+        self.view.bringSubviewToFront(ViewController.postButton)
+        self.view.bringSubviewToFront(ViewController.middleButton)
+        ViewController.middleButton.isHidden = false
+    }
+    
     public static func removeMiddleButton() {
 //        ViewController.chatButton.removeFromSuperview()
 //        ViewController.callButton.removeFromSuperview()

BIN
NexilisLite/.DS_Store


+ 8 - 0
NexilisLite/NexilisLite/Source/APIS.swift

@@ -2397,6 +2397,14 @@ public class APIS: NSObject {
         return nil
     }
     
+    public static func monitoredActivity() {
+        UIViewController.swizzleViewDidAppearImplementation
+        UINavigationController.swizzlePushViewControllerImplementation
+        UINavigationController.swizzlePopViewControllerImplementation
+        _ = DataCaptured()
+        UIApplication.swizzleSendAction
+    }
+    
     private static var appNm = "";
     public static func getAppNm() -> String {
         return appNm

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

@@ -2784,4 +2784,62 @@ public class CoreMessage_TMessageBank {
         return tMessage
     }
     
+    public static func getCreateCommunity(pCommunityName: String, pCommunityDesc: String, pCommunityThumbId: String) -> TMessage {
+        let tMessage = NexilisLite.TMessage()
+        let me = User.getMyPin() ?? ""
+        tMessage.mPIN = me
+        tMessage.mCode = CoreMessage_TMessageCode.CREATE_COMMUNITY
+        tMessage.mStatus = CoreMessage_TMessageUtil.getTID()
+        tMessage.mBodies[CoreMessage_TMessageKey.NAME] = pCommunityName
+        tMessage.mBodies[CoreMessage_TMessageKey.DESCRIPTION] = pCommunityDesc
+        tMessage.mBodies[CoreMessage_TMessageKey.THUMB_ID] = pCommunityThumbId
+        return tMessage
+    }
+    
+    public static func getDLP(fileName: String, type: String, reason: String) -> TMessage {
+        let tMessage = NexilisLite.TMessage()
+        let me = User.getMyPin() ?? ""
+        tMessage.mPIN = me
+        tMessage.mCode = CoreMessage_TMessageCode.DATA_LOSS_PREVENTION
+        tMessage.mStatus = CoreMessage_TMessageUtil.getTID()
+        tMessage.mBodies[CoreMessage_TMessageKey.FILE_NAME] = fileName
+        tMessage.mBodies[CoreMessage_TMessageKey.TYPE] = type
+        tMessage.mBodies[CoreMessage_TMessageKey.REASON] = reason
+        return tMessage
+    }
+    
+    public static func getLogActivity(pActivityClassName: String) -> TMessage {
+        let tMessage = NexilisLite.TMessage()
+        let me = User.getMyPin() ?? ""
+        tMessage.mPIN = me
+        tMessage.mCode = CoreMessage_TMessageCode.LOG_ACTIVITY
+        tMessage.mStatus = CoreMessage_TMessageUtil.getTID()
+        tMessage.mBodies[CoreMessage_TMessageKey.ACTVITY] = pActivityClassName
+        tMessage.mBodies[CoreMessage_TMessageKey.LOCAL_TIMESTAMP] = "\(Date().currentTimeMillis())"
+        return tMessage
+    }
+    
+    public static func getLogMonitor(type: String, label: String, value: String) -> TMessage {
+        let tMessage = NexilisLite.TMessage()
+        let me = User.getMyPin() ?? ""
+        tMessage.mPIN = me
+        tMessage.mCode = CoreMessage_TMessageCode.ACTIVITY_MONITORING
+        tMessage.mStatus = CoreMessage_TMessageUtil.getTID()
+        tMessage.mBodies[CoreMessage_TMessageKey.ACT_TYPE] = type
+        tMessage.mBodies[CoreMessage_TMessageKey.ACT_LABEL] = label
+        tMessage.mBodies[CoreMessage_TMessageKey.ACT_VALUE] = value
+        tMessage.mBodies[CoreMessage_TMessageKey.ACT_TIME] = "\(Date().currentTimeMillis())"
+        return tMessage
+    }
+    
+    public static func getCaptureDLP(data: String) -> TMessage {
+        let tMessage = NexilisLite.TMessage()
+        let me = User.getMyPin() ?? ""
+        tMessage.mPIN = me
+        tMessage.mCode = CoreMessage_TMessageCode.DATA_LOSS_PREVENTION
+        tMessage.mStatus = CoreMessage_TMessageUtil.getTID()
+        tMessage.mBodies[CoreMessage_TMessageKey.DATA] = data
+        return tMessage
+    }
+    
 }

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

@@ -811,5 +811,22 @@ public class CoreMessage_TMessageCode {
     
     public static let AUTH_REQUEST = "AR01";
     public static let CHECK_USER_MSISDN = "CUM1";
+    public static let DATA_LOSS_PREVENTION = "DLP";
+    public static let LOG_ACTIVITY = "ACLG";
+    public static let ACTIVITY_MONITORING = "ACTM";
+    
+    public static let CREATE_COMMUNITY = "CM01";
+    public static let GET_COMMUNITY_GROUPS = "CM02";
+    public static let GET_COMMUNITY_MEMBERS = "CM03";
+    public static let INVITE_COMMUNITY_MEMBERS = "CM04";
+    public static let PUSH_COMMUNITY = "CM05";
+    public static let GET_COMMUNITY = "CM06";
+    public static let EXIT_COMMUNITY = "CM07";
+    public static let INVITE_COMMUNITY_GROUP = "CM08";
+    public static let REMOVE_COMMUNITY_GROUP = "CM09";
+    public static let REMOVE_COMMUNITY = "CM10";
+    public static let CHANGE_COMMUNITY_INFO = "CM11";
+    public static let REPORT_COMMUNITY = "CM12";
+    public static let CHANGE_COMMUNITY_POSITION = "CM13";
 
 }

+ 8 - 0
NexilisLite/NexilisLite/Source/CoreMessage_TMessageKey.swift

@@ -506,4 +506,12 @@ public class CoreMessage_TMessageKey {
     public static let FINGERPRINT = "FPR";
     public static let SIGNATURE = "SIG";
     public static let PUBLIC_KEY = "PUK";
+    
+    public static let COMMUNITY_ID = "CMID";
+    public static let COMMUNITY_NAME = "CMNM";
+    
+    public static let ACT_LABEL = "ACTL";
+    public static let ACT_VALUE = "ACTV";
+    public static let ACT_TYPE = "ACTT";
+    public static let ACT_TIME = "ACTMS";
 }

+ 131 - 63
NexilisLite/NexilisLite/Source/Database.swift

@@ -102,6 +102,7 @@ public class Database {
                 addColumnIfNeeded(database: fmdb, tableName: "MESSAGE_SUMMARY", columnName: "archived", columnType: "INTEGER", defaultValue: "0")
                 addColumnIfNeeded(database: fmdb, tableName: "MESSAGE", columnName: "is_pinned", columnType: "TEXT", defaultValue: "0")
                 addColumnIfNeeded(database: fmdb, tableName: "MESSAGE", columnName: "attachment_speciality", columnType: "TEXT", defaultValue: "")
+                changeNameColumn(database: fmdb, tableName: "COMMUNITY", oldColumnName: "group_type", newColumnName: "community_type")
                 result = 1
 //                    print("Create Done")
             } catch {
@@ -144,7 +145,28 @@ public class Database {
         }
     }
     
-    func createDatabase(fmdb:FMDatabase) throws -> Void{
+    func changeNameColumn(database: FMDatabase, tableName: String, oldColumnName: String, newColumnName: String, ) {
+        do {
+            let rs = try database.executeQuery("PRAGMA table_info(\(tableName))", values: nil)
+            var columnExists = false
+            while rs.next() {
+                if let existingColumn = rs.string(forColumn: "name"), existingColumn == oldColumnName {
+                    columnExists = true
+                    break
+                }
+            }
+            rs.close()
+
+            if columnExists {
+                let alterSQL = "ALTER TABLE \(tableName) RENAME COLUMN \(oldColumnName) TO \(newColumnName);"
+                try database.executeUpdate(alterSQL, values: nil)
+            }
+        } catch {
+            print("Error checking or adding column: \(error.localizedDescription)")
+        }
+    }
+    
+    func createDatabase(fmdb:FMDatabase) throws -> Void {
         try fmdb.executeUpdate("CREATE TABLE IF NOT EXISTS 'BUDDY' (" +
                                 "'_id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
                                 "'f_pin' text NOT NULL UNIQUE," +
@@ -458,90 +480,90 @@ public class Database {
                                 ")", values: nil)
         
         try fmdb.executeUpdate("CREATE TABLE IF NOT EXISTS 'MESSAGE_FAVORITE' (" +
-        "'message_id' text PRIMARY KEY NOT NULL" +
-        ")", values: nil)
+                                "'message_id' text PRIMARY KEY NOT NULL" +
+                                ")", values: nil)
         
         try fmdb.executeUpdate("CREATE TABLE IF NOT EXISTS 'LINK_PREVIEW' (" +
-        "'id' text PRIMARY KEY NOT NULL," +
-        "'link' text NOT NULL UNIQUE," +
-        "'data_link' text," +
-        "'retry' integer DEFAULT 0" +
-        ")", values: nil)
+                                "'id' text PRIMARY KEY NOT NULL," +
+                                "'link' text NOT NULL UNIQUE," +
+                                "'data_link' text," +
+                                "'retry' integer DEFAULT 0" +
+                                ")", values: nil)
         
         try fmdb.executeUpdate("CREATE TABLE IF NOT EXISTS 'PULL_DB' (" +
-        "'id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
-        "'pull_type' text NOT NULL," +
-        "'pull_key' text NOT NULL DEFAULT ('0')," +
-        "'time' text" +
-        ")", values: nil)
+                                "'id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
+                                "'pull_type' text NOT NULL," +
+                                "'pull_key' text NOT NULL DEFAULT ('0')," +
+                                "'time' text" +
+                                ")", values: nil)
         
         try fmdb.executeUpdate("CREATE TABLE IF NOT EXISTS 'PREFS' (" +
-        "'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL," +
-        "'key' text UNIQUE," +
-        "'value' text" +
-        ")", values: nil)
+                                "'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL," +
+                                "'key' text UNIQUE," +
+                                "'value' text" +
+                                ")", values: nil)
         
         try fmdb.executeUpdate("CREATE TABLE IF NOT EXISTS 'CALL_CENTER_HISTORY' (" +
-        "'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL," +
-        "'type' integer NOT NULL," +
-        "'title' text," +
-        "'time' text," +
-        "'f_pin' text," +
-        "'data' text," +
-        "'time_end' text," +
-        "'complaint_id' text NOT NULL UNIQUE," +
-        "'members' text," +
-        "'requester' text" +
-        ")", values: nil)
+                                "'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL," +
+                                "'type' integer NOT NULL," +
+                                "'title' text," +
+                                "'time' text," +
+                                "'f_pin' text," +
+                                "'data' text," +
+                                "'time_end' text," +
+                                "'complaint_id' text NOT NULL UNIQUE," +
+                                "'members' text," +
+                                "'requester' text" +
+                                ")", values: nil)
         
         try fmdb.executeUpdate("CREATE TABLE IF NOT EXISTS 'FORM_DATA' (" +
-        "'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL," +
-        "'reff_id' TEXT NOT NULL UNIQUE," +
-        "'form_id' TEXT," +
-        "'title' text," +
-        "'description' text," +
-        "'created_by' text," +
-        "'created_date' text," +
-        "'target_date' text," +
-        "'status' integer" +
-        ")", values: nil)
+                                "'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL," +
+                                "'reff_id' TEXT NOT NULL UNIQUE," +
+                                "'form_id' TEXT," +
+                                "'title' text," +
+                                "'description' text," +
+                                "'created_by' text," +
+                                "'created_date' text," +
+                                "'target_date' text," +
+                                "'status' integer" +
+                                ")", values: nil)
         
         try fmdb.executeUpdate("CREATE TABLE IF NOT EXISTS 'TASK_PIC' (" +
-        "'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL," +
-        "'reff_id' text," +
-        "'sq_no' integer," +
-        "'ac' text," +
-        "'ac_desc' text," +
-        "'f_pin' text," +
-        "'submit_date' text," +
-        "'submit_type' text," +
-        "'submit_info' text," +
-        "'submit_id' text)", values: nil)
+                                "'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL," +
+                                "'reff_id' text," +
+                                "'sq_no' integer," +
+                                "'ac' text," +
+                                "'ac_desc' text," +
+                                "'f_pin' text," +
+                                "'submit_date' text," +
+                                "'submit_type' text," +
+                                "'submit_info' text," +
+                                "'submit_id' text)", values: nil)
         
         try fmdb.executeUpdate("CREATE INDEX IF NOT EXISTS TASK_PIC_UK1 on TASK_PIC (reff_id,submit_date,f_pin,submit_info)", values: nil)
         
         try fmdb.executeUpdate("CREATE TABLE IF NOT EXISTS 'TASK_DETAIL' (" +
-        "'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL," +
-        "'reff_id' text," +
-        "'key' text," +
-        "'value' text)", values: nil)
+                                "'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL," +
+                                "'reff_id' text," +
+                                "'key' text," +
+                                "'value' text)", values: nil)
         
         try fmdb.executeUpdate("CREATE INDEX IF NOT EXISTS TASK_DETAIL_UK1 on TASK_DETAIL (reff_id,key)", values: nil)
         
         try fmdb.executeUpdate("CREATE TABLE IF NOT EXISTS 'SERVICE_BANK' (" +
-        "'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL," +
-        "'service_id' text NOT NULL UNIQUE," +
-        "'service_name' text," +
-        "'description' text," +
-        "'parent' text," +
-        "'is_tablet' text)", values: nil)
+                                "'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL," +
+                                "'service_id' text NOT NULL UNIQUE," +
+                                "'service_name' text," +
+                                "'description' text," +
+                                "'parent' text," +
+                                "'is_tablet' text)", values: nil)
         
         try fmdb.executeUpdate("CREATE TABLE IF NOT EXISTS 'WORKING_AREA' (" +
-        "'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL," +
-        "'area_id' text NOT NULL UNIQUE," +
-        "'name' text," +
-        "'parent' text," +
-        "'level' text)", values: nil)
+                                "'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL," +
+                                "'area_id' text NOT NULL UNIQUE," +
+                                "'name' text," +
+                                "'parent' text," +
+                                "'level' text)", values: nil)
         
         try fmdb.executeUpdate("CREATE TABLE IF NOT EXISTS 'GROUP_NM' (" +
                                 "'_id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
@@ -565,6 +587,52 @@ public class Database {
                                 "'level_edu' INTEGER DEFAULT -1," +
                                 "'materi_edu' INTEGER DEFAULT -1," +
                                 "'is_education' INTEGER DEFAULT 0)", values: nil)
+        
+        try fmdb.executeUpdate("CREATE TABLE IF NOT EXISTS 'COMMUNITY' (" +
+                                "'_id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
+                                "'community_id' text NOT NULL UNIQUE," +
+                                "'f_name' text," +
+                                "'scope_id' text," +
+                                "'image_id' TEXT," +
+                                "'quote' text," +
+                                "'last_update' text," +
+                                "'created_by' text," +
+                                "'created_date' text," +
+                                "'ex_block' TEXT DEFAULT (0)," +
+                                "'folder_id' TEXT," +
+                                "'chat_modifier' INTEGER DEFAULT 1," +
+                                "'community_type' INTEGER DEFAULT 0," +
+                                "'parent' text," +
+                                "'level' text," +
+                                "'muted' INTEGER DEFAULT 0," +
+                                "'is_open' INTEGER DEFAULT 0," +
+                                "'official' INTEGER DEFAULT 0," +
+                                "'level_edu' INTEGER DEFAULT -1," +
+                                "'materi_edu' INTEGER DEFAULT -1," +
+                                "'is_education' INTEGER DEFAULT 0," +
+                                "'be' text," +
+                                "'sort_order' INTEGER DEFAULT 1)", values: nil)
+        
+        try fmdb.executeUpdate("CREATE TABLE IF NOT EXISTS 'COMMUNITY_GROUPZ' (" +
+                                "'community_id' TEXT NOT NULL," +
+                                "'group_id' TEXT NOT NULL," +
+                                "'created_date' TEXT DEFAULT (0)," +
+                                "'is_default' TEXT DEFAULT (0)," +
+                                "PRIMARY KEY ('community_id', 'group_id'))", values: nil)
+        
+        try fmdb.executeUpdate("CREATE TABLE IF NOT EXISTS 'COMMUNITY_MEMBER' (" +
+                               "'community_id' TEXT NOT NULL," +
+                               "'f_pin' TEXT NOT NULL," +
+                               "'position' TEXT DEFAULT (0)," +
+                               "'user_id' NOT NULL DEFAULT '-'," +
+                               "'ac' NOT NULL DEFAULT '-'," +
+                               "'ac_desc' NOT NULL DEFAULT '-'," +
+                               "'first_name' TEXT NOT NULL," +
+                               "'last_name' TEXT NOT NULL," +
+                               "'msisdn' TEXT NOT NULL," +
+                               "'thumb_id' TEXT NOT NULL," +
+                               "'created_date' TEXT DEFAULT (0)," +
+                               "PRIMARY KEY ('community_id', 'f_pin'))", values: nil)
     }
     
     public func executes(fmdb: FMDatabase, queries: [String]) {

+ 497 - 0
NexilisLite/NexilisLite/Source/Extension.swift

@@ -14,6 +14,11 @@ import MobileCoreServices
 import CommonCrypto
 import ZIPFoundation
 import PDFKit
+import ObjectiveC
+import CoreLocation
+import CoreTelephony
+import Network
+import SystemConfiguration.CaptiveNetwork
 
 extension Date {
     
@@ -671,6 +676,12 @@ extension URL {
                 return 1
             }
         }
+        
+        if matches.result != 1 {
+            DispatchQueue.global().async {
+                DataCaptured.sendErrorDLP(fileName: self.lastPathComponent, code: matches.result == -1 ? 22 : 21)
+            }
+        }
 
         return matches.result
     }
@@ -679,6 +690,9 @@ extension URL {
 
 extension UIColor {
     public static var mainColor: UIColor {
+        if Utils.getIsWATheme() {
+            return whatsappGreenColor
+        }
         return renderColor(hex: "#046cfc")
     }
     
@@ -1710,3 +1724,486 @@ extension Collection {
         return indices.contains(index) ? self[index] : nil
     }
 }
+
+extension UIViewController {
+    static let swizzleViewDidAppearImplementation: Void = {
+        let originalSelector = #selector(viewDidAppear(_:))
+        let swizzledSelector = #selector(swizzled_viewDidAppear(_:))
+
+        guard
+            let originalMethod = class_getInstanceMethod(UIViewController.self, originalSelector),
+            let swizzledMethod = class_getInstanceMethod(UIViewController.self, swizzledSelector)
+        else {
+            return
+        }
+
+        method_exchangeImplementations(originalMethod, swizzledMethod)
+    }()
+
+    @objc func swizzled_viewDidAppear(_ animated: Bool) {
+        swizzled_viewDidAppear(animated)
+
+        if self is UINavigationController || self is UITabBarController {
+            // If it's a UINavigationController, get its topViewController
+            if let nav = self as? UINavigationController,
+               let topVC = nav.topViewController {
+                DataCaptured.actVC = "\(String(describing: type(of: topVC)))"
+                DispatchQueue.global().async {
+                    DataCaptured.sendLogMonitorActivity()
+                }
+            }
+            return
+        }
+        
+        if "\(type(of: self))".hasPrefix("UI") {
+            return
+        }
+
+        // Normal screen
+        DataCaptured.actVC = "\(String(describing: type(of: self)))"
+        DispatchQueue.global().async {
+            DataCaptured.sendLogMonitorActivity()
+        }
+    }
+}
+
+extension UINavigationController {
+    static let swizzlePushViewControllerImplementation: Void = {
+        let originalSelector = #selector(pushViewController(_:animated:))
+        let swizzledSelector = #selector(swizzled_pushViewController(_:animated:))
+
+        guard
+            let originalMethod = class_getInstanceMethod(UINavigationController.self, originalSelector),
+            let swizzledMethod = class_getInstanceMethod(UINavigationController.self, swizzledSelector)
+        else {
+            return
+        }
+
+        method_exchangeImplementations(originalMethod, swizzledMethod)
+    }()
+
+    @objc func swizzled_pushViewController(_ viewController: UIViewController, animated: Bool) {
+        DataCaptured.actNC = "\(String(describing: type(of: viewController)))"
+        DataCaptured.actVC = ""
+        DispatchQueue.global().asyncAfter(deadline: .now() + 1, execute: {
+            if DataCaptured.actVC.isEmpty {
+                DataCaptured.sendLogMonitorActivity()
+            }
+        })
+        swizzled_pushViewController(viewController, animated: animated)
+    }
+    
+    static let swizzlePopViewControllerImplementation: Void = {
+        let originalSelector = #selector(popViewController(animated:))
+        let swizzledSelector = #selector(swizzled_popViewController(animated:))
+
+        guard
+            let originalMethod = class_getInstanceMethod(UINavigationController.self, originalSelector),
+            let swizzledMethod = class_getInstanceMethod(UINavigationController.self, swizzledSelector)
+        else {
+            return
+        }
+
+        method_exchangeImplementations(originalMethod, swizzledMethod)
+    }()
+
+    @objc func swizzled_popViewController(animated: Bool) -> UIViewController? {
+        let poppedVC = swizzled_popViewController(animated: animated)
+        if let topVC = self.topViewController {
+            DataCaptured.actNC = "\(String(describing: type(of: topVC)))"
+            DataCaptured.actVC = ""
+            DispatchQueue.global().asyncAfter(deadline: .now() + 1, execute: {
+                if DataCaptured.actVC.isEmpty {
+                    DataCaptured.sendLogMonitorActivity()
+                }
+            })
+        }
+        return poppedVC
+    }
+}
+
+extension UIApplication {
+    static let swizzleSendAction: Void = {
+        let original = #selector(sendAction(_:to:from:for:))
+        let swizzled = #selector(swizzled_sendAction(_:to:from:for:))
+
+        guard let originalMethod = class_getInstanceMethod(UIApplication.self, original),
+              let swizzledMethod = class_getInstanceMethod(UIApplication.self, swizzled) else { return }
+
+        method_exchangeImplementations(originalMethod, swizzledMethod)
+    }()
+
+    @objc func swizzled_sendAction(_ action: Selector, to target: Any?, from sender: Any?, for event: UIEvent?) -> Bool {
+        if let button = sender as? UIButton {
+            let title = button.titleLabel?.text ?? "Unnamed Button"
+            DataCaptured.action = "CLICKED"
+            DataCaptured.textAction = "\(title)"
+            DispatchQueue.global().async {
+                DataCaptured.sendLogMonitorAction()
+            }
+        }
+        return swizzled_sendAction(action, to: target, from: sender, for: event)
+    }
+
+    static func enableSwizzling() {
+        _ = swizzleSendAction
+    }
+}
+
+class DataCaptured: NSObject {
+    static var actVC = ""
+    static var actNC = ""
+    static var action = ""
+    static var textAction = ""
+    
+    override init() {
+        NotificationCenter.default.addObserver(
+            forName: UITextField.textDidChangeNotification,
+            object: nil,
+            queue: .main
+        ) { notification in
+            if let textField = notification.object as? UITextField {
+                DataCaptured.action = "TEXT_CHANGED"
+                DataCaptured.textAction = textField.text ?? ""
+                DispatchQueue.global().async {
+                    DataCaptured.sendLogMonitorAction()
+                }
+            }
+        }
+        
+        NotificationCenter.default.addObserver(
+            forName: UITextView.textDidChangeNotification,
+            object: nil,
+            queue: .main
+        ) { notification in
+            if let textView = notification.object as? UITextView {
+                DataCaptured.action = "TEXT_CHANGED"
+                DataCaptured.textAction = textView.text ?? ""
+                DispatchQueue.global().async {
+                    DataCaptured.sendLogMonitorAction()
+                }
+            }
+        }
+    }
+    static func sendLogMonitorAction() {
+        var type = "1"
+        var value = "1"
+        if action == "TEXT_CHANGED" {
+            type = "2"
+            value = textAction
+        }
+        print("sendLogMonitorAction: \(type) <><> \(textAction) <><> \(value)")
+//        _ = Nexilis.writeSync(message: CoreMessage_TMessageBank.getLogMonitor(type: type, label: textAction, value: value))
+    }
+    
+    static func sendLogMonitorActivity() {
+        var act = actNC
+        if !actVC.isEmpty {
+            act = actVC
+        }
+        print("sendLogMonitorActivity: \(act)")
+//        _ = Nexilis.write(message: CoreMessage_TMessageBank.getLogActivity(pActivityClassName: act))
+    }
+    
+    static func sendErrorDLP(fileName: String, code: Int) {
+        var data = collectDeviceAttributes()
+        data["security_shield"] = "\(code)"
+        data["filename"] = fileName
+        if let jsonData = try? JSONSerialization.data(withJSONObject: data, options: .prettyPrinted),
+           let jsonString = String(data: jsonData, encoding: .utf8) {
+            print("sendErrorDLP: \(jsonString)")
+//            _ = Nexilis.write(message: CoreMessage_TMessageBank.getCaptureDLP(data: jsonString))
+        }
+    }
+    
+    private var currentLocation: CLLocation?
+    private static func collectDeviceAttributes() -> [String: Any] {
+        var params: [String: Any] = [:]
+
+        // User and session
+        let me: String? = User.getMyPin() ?? ""
+        let sesId: String? = Utils.getConnectionID()
+        params["f_pin"] = me
+        params["session_id"] = sesId
+
+        // App info (replace with your preferences retrieval)
+        params["api"] = Nexilis.sAPIKey
+        params["app_id"] = APIS.getAppNm()
+        params["lib_version"] = Nexilis.cpaasVersion
+        params["app_version"] = Nexilis.cpaasVersion
+
+        // Network Info
+        let (netType, netTypeName) = getNetworkType()
+        let (operatorCode, operatorName) = getCarrierInfo()
+        let (wifiStatus, wifiIp, wifiSsid, wifiBssid) = getWifiInfo()
+        params["network_type"] = netType
+        params["network_type_name"] = netTypeName
+        params["network_operator"] = operatorCode
+        params["network_operator_name"] = operatorName
+        params["wifi_ssid"] = wifiSsid
+        params["wifi_bssid"] = wifiBssid
+        params["wifi_adapter"] = wifiStatus
+        params["wifi_ip"] = wifiIp
+        
+
+        // IP Address
+        params["ip_addressv4"] = getIPAddress(useIPv4: true)
+        params["ip_address"] = getIPAddress(useIPv4: false)
+
+        // GPS / location
+        let semaphore = DispatchSemaphore(value: 0)
+        
+        DispatchQueue.main.async {
+            LocationFetcher.shared.getCurrentLocation { coordinate in
+                var long = "0"
+                var lat = "0"
+                if let coord = coordinate {
+                    long = "\(coord.longitude)"
+                    lat = "\(coord.latitude)"
+                }
+//                print("Latitude: \(lat), Longitude: \(long)")
+                params["latitude"] = lat
+                params["longitude"] = long
+                semaphore.signal()
+            }
+        }
+        
+        _ = semaphore.wait(timeout: .now() + 10.0)
+
+        // iOS doesn't have an Android ID; use identifierForVendor
+        params["ios_identifier"] = UIDevice.current.identifierForVendor?.uuidString ?? ""
+
+        // Device attributes
+        let device = UIDevice.current
+        params["device_NAME"] = device.name
+        params["device_MODEL"] = device.model
+        params["device_SYSTEM_NAME"] = device.systemName
+        params["device_SYSTEM_VERSION"] = device.systemVersion
+        params["device_IDENTIFIER_FOR_VENDOR"] = device.identifierForVendor?.uuidString ?? ""
+
+        return getSimData(params: params)
+    }
+    
+    private static func getSimData(params: [String: Any] = [:]) -> [String: Any] {
+        var params = params
+        var simArray: [[String: Any]] = []
+
+        let networkInfo = CTTelephonyNetworkInfo()
+
+        if #available(iOS 12.0, *) {
+            if let carriers = networkInfo.serviceSubscriberCellularProviders {
+                for (key, carrier) in carriers {
+                    var simInfo: [String: Any] = [:]
+                    simInfo["carrier_name"] = carrier.carrierName ?? ""
+                    simInfo["mcc"] = carrier.mobileCountryCode ?? ""
+                    simInfo["mnc"] = carrier.mobileNetworkCode ?? ""
+                    simInfo["sim_slot"] = key // This is not a true "slot", but the key used internally
+                    simArray.append(simInfo)
+                }
+            }
+        } else {
+            if let carrier = networkInfo.subscriberCellularProvider {
+                var simInfo: [String: Any] = [:]
+                simInfo["carrier_name"] = carrier.carrierName ?? ""
+                simInfo["mcc"] = carrier.mobileCountryCode ?? ""
+                simInfo["mnc"] = carrier.mobileNetworkCode ?? ""
+                simInfo["sim_slot"] = "default"
+                simArray.append(simInfo)
+            }
+        }
+        params["sim_data"] = simArray
+
+        return params
+    }
+    
+    private static func getNetworkType() -> (type: String, name: String) {
+        let monitor = NWPathMonitor()
+        var networkType = ""
+        var networkTypeName = ""
+        
+        let semaphore = DispatchSemaphore(value: 0)
+        monitor.pathUpdateHandler = { path in
+            if path.usesInterfaceType(.wifi) {
+                networkType = "1" // Corresponds to TYPE_WIFI in Android
+                networkTypeName = "WIFI"
+            } else if path.usesInterfaceType(.cellular) {
+                networkType = "0" // Corresponds to TYPE_MOBILE
+                networkTypeName = "MOBILE"
+            } else {
+                networkType = "-1"
+                networkTypeName = "UNKNOWN"
+            }
+            semaphore.signal()
+            monitor.cancel()
+        }
+        let queue = DispatchQueue(label: "NetworkMonitor")
+        monitor.start(queue: queue)
+        semaphore.wait()
+        
+        return (networkType, networkTypeName)
+    }
+    
+    private static func getCarrierInfo() -> (operatorCode: String, operatorName: String) {
+        let networkInfo = CTTelephonyNetworkInfo()
+        
+        var carrierCode = ""
+        var carrierName = ""
+        
+        if #available(iOS 12.0, *) {
+            if let carriers = networkInfo.serviceSubscriberCellularProviders {
+                for (_, carrier) in carriers {
+                    carrierCode = (carrier.mobileCountryCode ?? "") + (carrier.mobileNetworkCode ?? "")
+                    carrierName = carrier.carrierName ?? ""
+                    break // Just use the first one
+                }
+            }
+        } else {
+            if let carrier = networkInfo.subscriberCellularProvider {
+                carrierCode = (carrier.mobileCountryCode ?? "") + (carrier.mobileNetworkCode ?? "")
+                carrierName = carrier.carrierName ?? ""
+            }
+        }
+        
+        return (carrierCode, carrierName)
+    }
+    
+    private static func getWifiInfo() -> (adapter: String, ip: String, ssid: String, bssid: String) {
+        var adapterStatus = "Off"
+        var ipAddress = ""
+        var ssid = ""
+        var bssid = ""
+        
+        // Get IP Address
+        if let interfaces = CNCopySupportedInterfaces() as NSArray? {
+            for interfaceName in interfaces {
+                if let unsafeInterfaceData = CNCopyCurrentNetworkInfo(interfaceName as! CFString) as NSDictionary? {
+                    ssid = unsafeInterfaceData["SSID"] as? String ?? ""
+                    bssid = unsafeInterfaceData["BSSID"] as? String ?? ""
+                    adapterStatus = "Connected"
+                    break
+                }
+            }
+        }
+        
+        if ssid.isEmpty {
+            adapterStatus = "Not Connected"
+        }
+
+        ipAddress = getWiFiIPAddress() ?? ""
+
+        return (adapterStatus, ipAddress, ssid, bssid)
+    }
+
+    private static func getWiFiIPAddress() -> String? {
+        var address: String?
+
+        var ifaddr: UnsafeMutablePointer<ifaddrs>?
+        guard getifaddrs(&ifaddr) == 0, let firstAddr = ifaddr else { return nil }
+
+        for ptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {
+            let interface = ptr.pointee
+            let addrFamily = interface.ifa_addr.pointee.sa_family
+
+            if addrFamily == UInt8(AF_INET) || addrFamily == UInt8(AF_INET6) {
+                let name = String(cString: interface.ifa_name)
+                if name == "en0" { // en0 is Wi-Fi
+                    var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
+                    getnameinfo(interface.ifa_addr, socklen_t(interface.ifa_addr.pointee.sa_len),
+                                &hostname, socklen_t(hostname.count),
+                                nil, socklen_t(0), NI_NUMERICHOST)
+                    address = String(cString: hostname)
+                    break
+                }
+            }
+        }
+
+        freeifaddrs(ifaddr)
+        return address
+    }
+    
+    private static func getIPAddress(useIPv4: Bool) -> String {
+        var address: String = ""
+
+        var ifaddr: UnsafeMutablePointer<ifaddrs>? = nil
+        if getifaddrs(&ifaddr) == 0, let firstAddr = ifaddr {
+            for ptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {
+                let interface = ptr.pointee
+                let addrFamily = interface.ifa_addr.pointee.sa_family
+
+                if addrFamily == UInt8(AF_INET) || addrFamily == UInt8(AF_INET6) {
+                    let name = String(cString: interface.ifa_name)
+                    if name == "en0" || name == "pdp_ip0" {
+                        var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
+                        let result = getnameinfo(
+                            interface.ifa_addr,
+                            socklen_t(interface.ifa_addr.pointee.sa_len),
+                            &hostname,
+                            socklen_t(hostname.count),
+                            nil,
+                            socklen_t(0),
+                            NI_NUMERICHOST
+                        )
+
+                        if result == 0 {
+                            let ip = String(cString: hostname)
+                            let isIPv4 = ip.contains(":") == false
+                            if useIPv4 && isIPv4 {
+                                address = ip
+                                break
+                            } else if !useIPv4 && !isIPv4 {
+                                // Remove IPv6 scope if present
+                                let cleanIPv6 = ip.split(separator: "%").first.map(String.init) ?? ip
+                                address = cleanIPv6.uppercased()
+                                break
+                            }
+                        }
+                    }
+                }
+            }
+            freeifaddrs(ifaddr)
+        }
+
+        return address
+    }
+}
+
+private class LocationFetcher: NSObject, CLLocationManagerDelegate {
+    static var shared = LocationFetcher()
+    private var manager: CLLocationManager?
+    private var completion: ((CLLocationCoordinate2D?) -> Void)?
+    
+    func getCurrentLocation(completion: @escaping (CLLocationCoordinate2D?) -> Void) {
+        self.completion = completion
+        self.manager = CLLocationManager()
+        self.manager?.delegate = self
+        self.manager?.desiredAccuracy = kCLLocationAccuracyBest
+        self.manager?.requestWhenInUseAuthorization()
+        
+        if CLLocationManager.locationServicesEnabled() {
+            self.manager?.requestLocation()
+        } else {
+            completion(nil)
+        }
+    }
+    
+    // MARK: - CLLocationManagerDelegate
+    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
+        completion?(locations.last?.coordinate)
+        cleanup()
+    }
+
+    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
+        print("Error: \(error.localizedDescription)")
+        completion?(nil)
+        cleanup()
+    }
+    
+    private func cleanup() {
+        manager?.stopUpdatingLocation()
+        manager?.delegate = nil
+        manager = nil
+        completion = nil
+    }
+}
+
+

+ 57 - 34
NexilisLite/NexilisLite/Source/MasterKeyUtil.swift

@@ -85,48 +85,71 @@ public class MasterKeyUtil {
         }
     }
     
+    private let masterKeyQueue = DispatchQueue(label: "io.nexilis.masterKeyQueue")
+
     func getMasterKey() throws -> SymmetricKey {
-        if Nexilis.checkingAccess(key: "authentication") && isDeviceNotSecure() && Nexilis.dispatch == nil {
-            var result = false
-            Nexilis.dispatch = DispatchGroup()
-            Nexilis.dispatch?.enter()
-            Utils.authenticateWithBiometrics { success, errorMessage in
-                if success {
-                    print("Access granted!")
-                    result = true
-                } else {
-                    print("Access denied: \(errorMessage ?? "Unknown error")")
+        var retrievedKey: SymmetricKey?
+        var thrownError: Error?
+
+        masterKeyQueue.sync {
+            if Nexilis.checkingAccess(key: "authentication") && isDeviceNotSecure() {
+                let semaphore = DispatchSemaphore(value: 0)
+                var result = false
+
+                Utils.authenticateWithBiometrics { success, errorMessage in
+                    if success {
+                        print("Access granted!")
+                        result = true
+                    } else {
+                        print("Access denied: \(errorMessage ?? "Unknown error")")
+                    }
+                    semaphore.signal()
                 }
-                if let dispatch = Nexilis.dispatch {
-                    dispatch.leave()
+
+                semaphore.wait()
+
+                if !result {
+                    DispatchQueue.main.async {
+                        Utils.showAlert(
+                            title: "Failed to get Master Key".localized(),
+                            message: "Biometric authentication hasn't been set up/Biometric invalid.".localized()
+                        )
+                    }
+                    thrownError = NSError(domain: "KeychainError", code: -99, userInfo: nil)
+                    return
                 }
             }
-            Nexilis.dispatch?.wait()
-            Nexilis.dispatch = nil
-            if !result {
-                DispatchQueue.main.async {
-                    Utils.showAlert(title: "Failed to get Master Key".localized(), message: "Biometric authentication hasn't been set up/Biometric invalid.".localized())
-                }
-                throw NSError(domain: "KeychainError", code: -99, userInfo: nil)
+
+            let query: [String: Any] = [
+                kSecClass as String: kSecClassKey,
+                kSecAttrApplicationTag as String: keyAlias,
+                kSecReturnData as String: true
+            ]
+
+            var item: CFTypeRef?
+            let status = SecItemCopyMatching(query as CFDictionary, &item)
+            guard status == errSecSuccess else {
+                thrownError = NSError(domain: "KeychainError", code: Int(status), userInfo: nil)
+                return
+            }
+
+            guard let keyData = item as? Data else {
+                thrownError = NSError(domain: "KeyRetrievalError", code: -1, userInfo: nil)
+                return
             }
+
+            retrievedKey = SymmetricKey(data: keyData)
         }
-        let query: [String: Any] = [
-            kSecClass as String: kSecClassKey,
-            kSecAttrApplicationTag as String: keyAlias,
-            kSecReturnData as String: true
-        ]
-        
-        var item: CFTypeRef?
-        let status = SecItemCopyMatching(query as CFDictionary, &item)
-        guard status == errSecSuccess else {
-            throw NSError(domain: "KeychainError", code: Int(status), userInfo: nil)
+
+        if let error = thrownError {
+            throw error
         }
-        
-        guard let keyData = item as? Data else {
-            throw NSError(domain: "KeyRetrievalError", code: -1, userInfo: nil)
+
+        guard let key = retrievedKey else {
+            throw NSError(domain: "KeychainError", code: -2, userInfo: [NSLocalizedDescriptionKey: "Failed to get key"])
         }
-        
-        return SymmetricKey(data: keyData)
+
+        return key
     }
     
     func getPrefsKey() throws -> SymmetricKey {

+ 10 - 5
NexilisLite/NexilisLite/Source/Model/User.swift

@@ -22,6 +22,7 @@ public class User: Model {
     public var ex_offmp: String?
     public var device_id: String
     public var status: String
+    public var beId: String
     
     public var isSelected: Bool = false
     public var isMuted: Bool = false
@@ -39,9 +40,10 @@ public class User: Model {
         self.ex_offmp = ""
         self.device_id = ""
         self.status = ""
+        self.beId = ""
     }
     
-    public init(pin: String, firstName: String, lastName: String, thumb: String, userType: String = "0", privacy_flag: String = "", offline_mode: String = "", ex_block: String = "", official: String = "", ex_offmp: String = "", device_id: String = "", status: String = "") {
+    public init(pin: String, firstName: String, lastName: String, thumb: String, userType: String = "0", privacy_flag: String = "", offline_mode: String = "", ex_block: String = "", official: String = "", ex_offmp: String = "", device_id: String = "", status: String = "", beId: String = "") {
         self.pin = pin
         self.firstName = firstName
         self.lastName = lastName
@@ -54,6 +56,7 @@ public class User: Model {
         self.ex_offmp = ex_offmp
         self.device_id = device_id
         self.status = status
+        self.beId = beId
     }
     
     public static func == (lhs: User, rhs: User) -> Bool {
@@ -134,7 +137,7 @@ public class User: Model {
         }
         var user: User?
         if fmdb != nil {
-            if let cursor = Database.shared.getRecords(fmdb: fmdb!, query: "select f_pin, first_name, last_name, image_id, user_type, privacy_flag, offline_mode, ex_block, device_id, official_account, quote from BUDDY where f_pin = '\(pin)' OR device_id = '\(pin)'"), cursor.next() {
+            if let cursor = Database.shared.getRecords(fmdb: fmdb!, query: "select f_pin, first_name, last_name, image_id, user_type, privacy_flag, offline_mode, ex_block, device_id, official_account, quote, be_info from BUDDY where f_pin = '\(pin)' OR device_id = '\(pin)'"), cursor.next() {
                 user = User(pin: cursor.string(forColumnIndex: 0) ?? "",
                             firstName: cursor.string(forColumnIndex: 1) ?? "",
                             lastName: cursor.string(forColumnIndex: 2) ?? "",
@@ -145,7 +148,8 @@ public class User: Model {
                             ex_block: cursor.string(forColumnIndex: 7) ?? "",
                             official: cursor.string(forColumnIndex: 9) ?? "",
                             device_id: cursor.string(forColumnIndex: 8) ?? "",
-                            status: cursor.string(forColumnIndex: 10) ?? "")
+                            status: cursor.string(forColumnIndex: 10) ?? "",
+                            beId: cursor.string(forColumnIndex: 11) ?? "")
                 cursor.close()
             } else if let cursor = Database.shared.getRecords(fmdb: fmdb!, query: """
                                                               SELECT a.f_pin, a.first_name, a.last_name, a.thumb_id
@@ -180,7 +184,7 @@ public class User: Model {
         } else {
             Database.shared.database?.inTransaction({ fmdb, rollback in
                 do {
-                    if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select f_pin, first_name, last_name, image_id, user_type, privacy_flag, offline_mode, ex_block, device_id, official_account, quote from BUDDY where f_pin = '\(pin)' OR device_id = '\(pin)'"), cursor.next() {
+                    if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select f_pin, first_name, last_name, image_id, user_type, privacy_flag, offline_mode, ex_block, device_id, official_account, quote, be_info from BUDDY where f_pin = '\(pin)' OR device_id = '\(pin)'"), cursor.next() {
                         user = User(pin: cursor.string(forColumnIndex: 0) ?? "",
                                     firstName: cursor.string(forColumnIndex: 1) ?? "",
                                     lastName: cursor.string(forColumnIndex: 2) ?? "",
@@ -191,7 +195,8 @@ public class User: Model {
                                     ex_block: cursor.string(forColumnIndex: 7) ?? "",
                                     official: cursor.string(forColumnIndex: 9) ?? "",
                                     device_id: cursor.string(forColumnIndex: 8) ?? "",
-                                    status: cursor.string(forColumnIndex: 10) ?? "")
+                                    status: cursor.string(forColumnIndex: 10) ?? "",
+                                    beId: cursor.string(forColumnIndex: 11) ?? "")
                         cursor.close()
                     } else if let cursor = Database.shared.getRecords(fmdb: fmdb, query: """
                                                               SELECT a.f_pin, a.first_name, a.last_name, a.thumb_id

+ 29 - 13
NexilisLite/NexilisLite/Source/Nexilis.swift

@@ -19,7 +19,7 @@ import CryptoKit
 import WebKit
 
 public class Nexilis: NSObject {
-    public static var cpaasVersion = "5.0.51"
+    public static var cpaasVersion = "5.0.52"
     public static var sAPIKey = ""
     
     public static var ADDRESS = ""
@@ -1410,23 +1410,39 @@ public class Nexilis: NSObject {
     
     private static var listDispatchGroups = [String: DispatchGroup]()
     private static var waitQueue = [String: TMessage]()
+    private static let syncQueue = DispatchQueue(label: "io.nexilis.syncQueue")
     
     public static func writeAndWait(message: TMessage, timeout: Int = 15 * 1000) -> TMessage? {
-        listDispatchGroups[message.getStatus()] = DispatchGroup()
-        let groupWait = listDispatchGroups[message.getStatus()]
-        groupWait?.enter()
-        waitQueue[message.getStatus()] = message
-//        print("wandw req: \(message.getCode())")
+        if message.getStatus().isEmpty {
+            return nil
+        }
+
+        var groupWait: DispatchGroup?
+
+        syncQueue.sync {
+            listDispatchGroups[message.getStatus()] = DispatchGroup()
+            groupWait = listDispatchGroups[message.getStatus()]
+            groupWait?.enter()
+            waitQueue[message.getStatus()] = message
+        }
+
         _ = write(message: message, timeout: timeout)
-        if groupWait?.wait(timeout: .now() + 15) == .timedOut {
-//            print("wandw timedOut: \(message.getCode())")
-            waitQueue.removeValue(forKey: message.getStatus())
-            listDispatchGroups.removeValue(forKey: message.getStatus())
-            groupWait?.leave()
+
+        if groupWait?.wait(timeout: .now() + .milliseconds(timeout)) == .timedOut {
+            syncQueue.sync {
+                waitQueue.removeValue(forKey: message.getStatus())
+                listDispatchGroups.removeValue(forKey: message.getStatus())
+                groupWait?.leave()
+            }
             return nil
         }
-        listDispatchGroups.removeValue(forKey: message.getStatus())
-        return waitQueue.removeValue(forKey: message.getStatus())
+
+        var response: TMessage?
+        syncQueue.sync {
+            listDispatchGroups.removeValue(forKey: message.getStatus())
+            response = waitQueue.removeValue(forKey: message.getStatus())
+        }
+        return response
     }
     
     static func incomingData(packetId: String, data: AnyObject) {

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

@@ -1501,6 +1501,14 @@ public final class Utils {
         return ""
     }
     
+    public static func setIsWATheme(value: Bool) {
+        SecureUserDefaults.shared.set(value, forKey: "is_wa_key")
+    }
+    public static func getIsWATheme() -> Bool {
+        let value: Bool = SecureUserDefaults.shared.value(forKey: "is_wa_key") ?? false
+        return value
+    }
+    
     static func getPasswordDB() -> String? {
         do {
             let p = getPassEncDB()

+ 6 - 0
NexilisLite/NexilisLite/Source/View/BNIView/BNIBookingWebView.swift

@@ -689,6 +689,12 @@ public class BNIBookingWebView: UIViewController, WKNavigationDelegate, UIScroll
         let disiniKey = "1711024221024"
         let gudegKey = "1712052403416"
         let kmiKey = "1713407687550"
+        let waKey = "1744166263877"
+        if appId == waKey {
+            Utils.setIsWATheme(value: true)
+        } else {
+            Utils.setIsWATheme(value: false)
+        }
         switch appId {
         case digisalesKey:
             UIApplication.shared.setAlternateIconName("digisales_icon")

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

@@ -631,26 +631,26 @@ public class ChatGPTBotView: UIViewController, UIGestureRecognizerDelegate {
                 if let cursorData = Database.shared.getRecords(fmdb: fmdb, query: query) {
                     while cursorData.next() {
                         var row: [String: Any?] = [:]
-                        row["message_id"] = cursorData.string(forColumnIndex: 0)
-                        row["f_pin"] = cursorData.string(forColumnIndex: 1)
-                        row["l_pin"] = cursorData.string(forColumnIndex: 2)
-                        row["message_scope_id"] = cursorData.string(forColumnIndex: 3)
-                        row["server_date"] = cursorData.string(forColumnIndex: 4)
-                        row["status"] = cursorData.string(forColumnIndex: 5)
-                        row["message_text"] = cursorData.string(forColumnIndex: 6)
-                        row["audio_id"] = cursorData.string(forColumnIndex: 7)
-                        row["video_id"] = cursorData.string(forColumnIndex: 8)
-                        row["image_id"] = cursorData.string(forColumnIndex: 9)
-                        row["thumb_id"] = cursorData.string(forColumnIndex: 10)
-                        row["read_receipts"] = cursorData.string(forColumnIndex: 11)
-                        row["chat_id"] = cursorData.string(forColumnIndex: 12)
-                        row["file_id"] = cursorData.string(forColumnIndex: 13)
-                        row["attachment_flag"] = cursorData.string(forColumnIndex: 14)
-                        row["reff_id"] = cursorData.string(forColumnIndex: 15)
-                        row["lock"] = cursorData.string(forColumnIndex: 16)
-                        row["is_stared"] = cursorData.string(forColumnIndex: 17)
-                        row["blog_id"] = cursorData.string(forColumnIndex: 18)
-                        row["credential"] = cursorData.string(forColumnIndex: 19)
+                        row["message_id"] = cursorData.string(forColumnIndex: 0) ?? ""
+                        row["f_pin"] = cursorData.string(forColumnIndex: 1) ?? ""
+                        row["l_pin"] = cursorData.string(forColumnIndex: 2) ?? ""
+                        row["message_scope_id"] = cursorData.string(forColumnIndex: 3) ?? ""
+                        row["server_date"] = cursorData.string(forColumnIndex: 4) ?? ""
+                        row["status"] = cursorData.string(forColumnIndex: 5) ?? ""
+                        row["message_text"] = cursorData.string(forColumnIndex: 6) ?? ""
+                        row["audio_id"] = cursorData.string(forColumnIndex: 7) ?? ""
+                        row["video_id"] = cursorData.string(forColumnIndex: 8) ?? ""
+                        row["image_id"] = cursorData.string(forColumnIndex: 9) ?? ""
+                        row["thumb_id"] = cursorData.string(forColumnIndex: 10) ?? ""
+                        row["read_receipts"] = cursorData.string(forColumnIndex: 11) ?? ""
+                        row["chat_id"] = cursorData.string(forColumnIndex: 12) ?? ""
+                        row["file_id"] = cursorData.string(forColumnIndex: 13) ?? ""
+                        row["attachment_flag"] = cursorData.string(forColumnIndex: 14) ?? ""
+                        row["reff_id"] = cursorData.string(forColumnIndex: 15) ?? ""
+                        row["lock"] = cursorData.string(forColumnIndex: 16) ?? ""
+                        row["is_stared"] = cursorData.string(forColumnIndex: 17) ?? ""
+                        row["blog_id"] = cursorData.string(forColumnIndex: 18) ?? ""
+                        row["credential"] = cursorData.string(forColumnIndex: 19) ?? ""
                         if let cursorStatus = Database.shared.getRecords(fmdb: fmdb, query: "SELECT status FROM MESSAGE_STATUS WHERE message_id='\(row["message_id"] as! String)'") {
                             while cursorStatus.next() {
                                 row["status"] = cursorStatus.string(forColumnIndex: 0)

+ 110 - 5
NexilisLite/NexilisLite/Source/View/Chat/CommunityNew.swift

@@ -7,14 +7,17 @@
 
 import Foundation
 import UIKit
+import NotificationBannerSwift
 
-public class CommunityNew: UIViewController {
+public class CommunityNew: UIViewController, UITextFieldDelegate  {
     
     let containerButton = UIView()
     let buttonComm = UIButton(type: .custom)
     let scrollView = UIScrollView()
     var bottomConstButton: NSLayoutConstraint!
     let fieldName = UITextField()
+    let fieldDesc = UITextView()
+    var thumb = ""
     
     public override func viewDidLoad() {
         setUpHeader()
@@ -103,7 +106,7 @@ public class CommunityNew: UIViewController {
         let ppView = UIImageView()
         ppView.backgroundColor = .whatsappGrayPPColor
         scrollView.addSubview(ppView)
-        ppView.anchor(top: scrollView.topAnchor, paddingTop: 115, centerX: scrollView.centerXAnchor, width: 120, height: 120)
+        ppView.anchor(top: scrollView.topAnchor, paddingTop: 80, centerX: scrollView.centerXAnchor, width: 120, height: 120)
         ppView.layer.cornerRadius = 25
         ppView.clipsToBounds = true
         ppView.image = UIImage(systemName: "person.3.fill")
@@ -126,6 +129,8 @@ public class CommunityNew: UIViewController {
         fieldName.clipsToBounds = true
         fieldName.tintColor = .whatsappGreenColor
         fieldName.textColor = .black
+        fieldName.addTarget(self, action: #selector(didChanged(sender:)), for: .editingChanged)
+        fieldName.delegate = self
         let leftPadding = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 0))
         fieldName.leftView = leftPadding
         fieldName.leftViewMode = .always
@@ -133,7 +138,6 @@ public class CommunityNew: UIViewController {
         fieldName.rightView = rightPadding
         fieldName.rightViewMode = .always
         
-        let fieldDesc = UITextView()
         scrollView.addSubview(fieldDesc)
         fieldDesc.font = .systemFont(ofSize: 14)
         fieldDesc.backgroundColor = .white
@@ -148,8 +152,6 @@ public class CommunityNew: UIViewController {
         
         containerButton.addSubview(buttonComm)
         buttonComm.anchor(top: containerButton.topAnchor, left: containerButton.leftAnchor, right: containerButton.rightAnchor, paddingTop: 5, paddingLeft: 30, paddingRight: 30, height: 45)
-//        buttonComm.backgroundColor = .whatsappGreenColor
-//        buttonComm.setTitleColor(.white, for: .normal)
         buttonComm.backgroundColor = .waGrayLight
         buttonComm.setTitleColor(.waGrayFont, for: .normal)
         buttonComm.layer.cornerRadius = 15
@@ -157,9 +159,112 @@ public class CommunityNew: UIViewController {
         buttonComm.setTitle("Create Community".localized(), for: .normal)
         buttonComm.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
         buttonComm.isEnabled = false
+//        buttonComm.addTarget(self, action: #selector(didSubmit), for: .touchUpInside)
+    }
+    
+    public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
+        if textField != fieldName {
+            return true
+        }
+        guard let currentText = textField.text else { return true }
+        let newLength = currentText.count + string.count - range.length
+        return newLength <= 100
+    }
+
+    @objc func didChanged(sender: Any) {
+        if let text = fieldName.text, text.trimmingCharacters(in: .whitespaces).isEmpty {
+            buttonComm.backgroundColor = .waGrayLight
+            buttonComm.setTitleColor(.waGrayFont, for: .normal)
+        } else {
+            buttonComm.backgroundColor = .whatsappGreenColor
+            buttonComm.setTitleColor(.white, for: .normal)
+        }
     }
     
     @objc func cancel(sender: Any) {
         self.dismiss(animated: true, completion: nil)
     }
+    
+    @objc func didSubmit(sender: Any) {
+        if let text = fieldName.text {
+            buttonComm.backgroundColor = .waGrayLight
+            buttonComm.setTitleColor(.waGrayFont, for: .normal)
+            
+            Nexilis.showLoader()
+            DispatchQueue.global().async {
+                if let resp = Nexilis.writeSync(message: CoreMessage_TMessageBank.getCreateCommunity(pCommunityName: text, pCommunityDesc: self.fieldDesc.text ?? "", pCommunityThumbId: self.thumb)) {
+                    if resp.isOk() {
+                        let communityId = resp.getBody(key: CoreMessage_TMessageKey.COMMUNITY_ID, default_value: "")
+                        if communityId.isEmpty {
+                            DispatchQueue.main.async {
+                                Nexilis.hideLoader(completion: {
+                                    let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
+                                    imageView.tintColor = .white
+                                    let banner = FloatingNotificationBanner(title: "Failed to create community, please try again later".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()
+                                })
+                            }
+                            return
+                        }
+                        Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                            let me = User.getMyPin() ?? ""
+                            let createdDate = Date().currentTimeMillis()
+                            if let dataMe = User.getData(pin: me, fmdb: fmdb) {
+                                do {
+                                    _ = try Database.shared.insertRecord(fmdb: fmdb, table: "COMMUNITY", cvalues: [
+                                        "community_id" : communityId,
+                                        "f_name" : text,
+                                        "image_id": self.thumb,
+                                        "quote": self.fieldDesc.text ?? "",
+                                        "created_by" : me,
+                                        "created_date" : "\(createdDate)",
+                                        "community_type" : 0,
+                                        "level" : 1,
+                                        "be" : dataMe.beId
+                                    ], replace: true)
+                                    
+                                    _ = try Database.shared.insertRecord(fmdb: fmdb, table: "COMMUNITY_MEMBER", cvalues: [
+                                        "community_id" :communityId,
+                                        "f_pin" : me,
+                                        "position" : 1,
+                                        "user_id" : me,
+                                        "ac" : "",
+                                        "ac_desc" : "",
+                                        "first_name" : dataMe.firstName,
+                                        "last_name" : dataMe.lastName,
+                                        "msisdn" : "",
+                                        "thumb_id" : dataMe.thumb,
+                                        "created_date" : "\(createdDate)"
+                                    ], replace: true)
+                                } catch {
+                                    rollback.pointee = true
+                                    print("Access database error: \(error.localizedDescription)")
+                                }
+                                
+                            }
+                        })
+                    } else {
+                        DispatchQueue.main.async {
+                            Nexilis.hideLoader(completion: {
+                                let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
+                                imageView.tintColor = .white
+                                let banner = FloatingNotificationBanner(title: "Failed to create community, please try again later".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()
+                            })
+                        }
+                    }
+                } else {
+                    DispatchQueue.main.async {
+                        Nexilis.hideLoader(completion: {
+                            let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
+                            imageView.tintColor = .white
+                            let banner = FloatingNotificationBanner(title: "Unable to access servers. Try again later".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()
+                        })
+                    }
+                }
+            }
+        }
+        
+    }
 }

+ 91 - 67
NexilisLite/NexilisLite/Source/View/Chat/EditorGroup.swift

@@ -208,7 +208,7 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
     public override func viewDidLoad() {
         super.viewDidLoad()
 //        navigationController?.navigationBar.topItem?.title = ""
-        Utils.addBackground(view: contactChatNav.view)
+        Utils.addBackground(view: self.view)
         if let dataWall = UserDefaults.standard.data(forKey: "chatWallpaper") {
             wallpaperView.image = UIImage(data: UserDefaults.standard.data(forKey: "chatWallpaper")!)
         }
@@ -2176,6 +2176,24 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
         let fPin = message.getBody(key: CoreMessage_TMessageKey.F_PIN)
         let scope = message.getBody(key: CoreMessage_TMessageKey.SCOPE_ID)
         message.mBodies[CoreMessage_TMessageKey.SERVER_DATE] = String(Date().currentTimeMillis())
+        if let listGroupImages = self.groupImages.first(where: { $0.key == message_id }) {
+            let valueListGroupImages = listGroupImages.value
+            message.mStatus = CoreMessage_TMessageUtil.getTID()
+            message.mBodies[CoreMessage_TMessageKey.L_PIN] = f_pin
+            var mId = ""
+            for i in 0..<valueListGroupImages.count {
+                if mId.isEmpty {
+                    mId = "-2,\(valueListGroupImages[i].messageId)"
+                } else {
+                    mId = mId + "," + valueListGroupImages[i].messageId
+                }
+            }
+            message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID] = mId
+        } else {
+            message.mStatus = CoreMessage_TMessageUtil.getTID()
+            message.mBodies[CoreMessage_TMessageKey.L_PIN] = f_pin
+            message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID] = "-2,\(message_id)"
+        }
         if (fPin.elementsEqual("-999") || scope.elementsEqual("16") || scope.elementsEqual("15")){
             return
         }
@@ -2188,39 +2206,40 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
                 if isBackground {
                     Thread.sleep(forTimeInterval: 1.0)
                 } else {
-                    if let listGroupImages = self.groupImages.first(where: { $0.key == message_id }) {
-                        let valueListGroupImages = listGroupImages.value
-                        for i in 0..<valueListGroupImages.count {
-                            Database.shared.database?.inTransaction({ (fmdb, rollback) in
-                                do {
-                                    _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
-                                        "status" : "4"
-                                    ], _where: "message_id = '\(valueListGroupImages[i].messageId)'")
-                                } catch {
-                                    rollback.pointee = true
-                                    print("Access database error: \(error.localizedDescription)")
+                    if let resp = Nexilis.writeAndWait(message: message) {
+                        if resp.isOk() {
+                            if let listGroupImages = self.groupImages.first(where: { $0.key == message_id }) {
+                                let valueListGroupImages = listGroupImages.value
+                                for i in 0..<valueListGroupImages.count {
+                                    Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                                        do {
+                                            _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
+                                                "status" : "4"
+                                            ], _where: "message_id = '\(valueListGroupImages[i].messageId)'")
+                                        } catch {
+                                            rollback.pointee = true
+                                            print("Access database error: \(error.localizedDescription)")
+                                        }
+                                    })
                                 }
-                            })
-                            message.mStatus = CoreMessage_TMessageUtil.getTID()
-                            message.mBodies[CoreMessage_TMessageKey.L_PIN] = f_pin
-                            message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID] = "-2,\(valueListGroupImages[i].messageId)"
+                            } else {
+                                Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                                    do {
+                                        _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
+                                            "status" : "4"
+                                        ], _where: "message_id = '\(message_id)'")
+                                    } catch {
+                                        rollback.pointee = true
+                                        print("Access database error: \(error.localizedDescription)")
+                                    }
+                                })
+                            }
+                        } else {
+                            self.sendReadMessageStatus(chat_id: chat_id, f_pin: fPin, message_scope_id: message_scope_id, message_id: message_id)
                         }
                     } else {
-                        Database.shared.database?.inTransaction({ (fmdb, rollback) in
-                            do {
-                                _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
-                                    "status" : "4"
-                                ], _where: "message_id = '\(message_id)'")
-                            } catch {
-                                rollback.pointee = true
-                                print("Access database error: \(error.localizedDescription)")
-                            }
-                        })
-                        message.mStatus = CoreMessage_TMessageUtil.getTID()
-                        message.mBodies[CoreMessage_TMessageKey.L_PIN] = f_pin
-                        message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID] = "-2,\(message_id)"
+                        self.sendReadMessageStatus(chat_id: chat_id, f_pin: fPin, message_scope_id: message_scope_id, message_id: message_id)
                     }
-                    _ = Nexilis.write(message: message)
                 }
             }
         }
@@ -2755,44 +2774,49 @@ extension EditorGroup: UITextViewDelegate, CustomTextViewPasteDelegate {
             hideMention()
         }
 
-        var nowTextFieldSend = self.textFieldSend
-        if isEditingMessage {
-            nowTextFieldSend = editTextView
-        }
-        let cursorPosition = textView.caretRect(for: nowTextFieldSend!.selectedTextRange!.start).origin
-        let doubleCurrentLine = cursorPosition.y / nowTextFieldSend!.font!.lineHeight
-        if doubleCurrentLine.isFinite {
-            let currentLine = Int(ceil(doubleCurrentLine))
-            UIView.animate(withDuration: 0.3) {
-                let layoutManager = textView.layoutManager
-                var numberOfLines = 0
-                var index = 0
-                let numberOfGlyphs = layoutManager.numberOfGlyphs
+        if var nowTextFieldSend = self.textFieldSend {
+            if isEditingMessage {
+                nowTextFieldSend = editTextView
+            }
+            if let sr = nowTextFieldSend.selectedTextRange {
+                if let fnt = nowTextFieldSend.font {
+                    let cursorPosition = textView.caretRect(for: sr.start).origin
+                    let doubleCurrentLine = cursorPosition.y / fnt.lineHeight
+                    if doubleCurrentLine.isFinite {
+                        let currentLine = Int(ceil(doubleCurrentLine))
+                        UIView.animate(withDuration: 0.3) {
+                            let layoutManager = textView.layoutManager
+                            var numberOfLines = 0
+                            var index = 0
+                            let numberOfGlyphs = layoutManager.numberOfGlyphs
 
-                while index < numberOfGlyphs {
-                    var lineRange = NSRange()
-                    layoutManager.lineFragmentRect(forGlyphAt: index, effectiveRange: &lineRange)
-                    index = NSMaxRange(lineRange)
-                    numberOfLines += 1
-                }
-                if currentLine == 1 && (numberOfLines == 1 || numberOfLines == 0) {
-                    if self.isEditingMessage {
-                        self.constraintHeighteditTextView.constant = 40
-                    } else {
-                        self.heightTextFieldSend.constant = 40
-                    }
-                } else if (self.heightTextFieldSend.constant < 95.0 || (self.constraintHeighteditTextView != nil && self.constraintHeighteditTextView.constant < 95.0)) && currentLine >= 4 {
-                    if self.isEditingMessage {
-                        self.constraintHeighteditTextView.constant = 95.0
-                    } else {
-                        self.heightTextFieldSend.constant = 95.0
-                    }
-                } else if currentLine < 4 && numberOfLines < 5 {
-                    if (nowTextFieldSend!.text.count > 0 && self.heightTextFieldSend.constant != nowTextFieldSend!.contentSize.height) {
-                        if self.isEditingMessage {
-                            self.constraintHeighteditTextView.constant = nowTextFieldSend!.contentSize.height
-                        } else {
-                            self.heightTextFieldSend.constant = nowTextFieldSend!.contentSize.height
+                            while index < numberOfGlyphs {
+                                var lineRange = NSRange()
+                                layoutManager.lineFragmentRect(forGlyphAt: index, effectiveRange: &lineRange)
+                                index = NSMaxRange(lineRange)
+                                numberOfLines += 1
+                            }
+                            if currentLine == 1 && (numberOfLines == 1 || numberOfLines == 0) {
+                                if self.isEditingMessage {
+                                    self.constraintHeighteditTextView.constant = 40
+                                } else {
+                                    self.heightTextFieldSend.constant = 40
+                                }
+                            } else if (self.heightTextFieldSend.constant < 95.0 || (self.constraintHeighteditTextView != nil && self.constraintHeighteditTextView.constant < 95.0)) && currentLine >= 4 {
+                                if self.isEditingMessage {
+                                    self.constraintHeighteditTextView.constant = 95.0
+                                } else {
+                                    self.heightTextFieldSend.constant = 95.0
+                                }
+                            } else if currentLine < 4 && numberOfLines < 5 {
+                                if (nowTextFieldSend.text.count > 0 && self.heightTextFieldSend.constant != nowTextFieldSend.contentSize.height) {
+                                    if self.isEditingMessage {
+                                        self.constraintHeighteditTextView.constant = nowTextFieldSend.contentSize.height
+                                    } else {
+                                        self.heightTextFieldSend.constant = nowTextFieldSend.contentSize.height
+                                    }
+                                }
+                            }
                         }
                     }
                 }

+ 91 - 67
NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift

@@ -188,7 +188,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
     public override func viewDidLoad() {
         super.viewDidLoad()
 //        navigationController?.navigationBar.topItem?.title = ""
-        Utils.addBackground(view: contactChatNav.view)
+        Utils.addBackground(view: self.view)
         if let dataWall = UserDefaults.standard.data(forKey: "chatWallpaper") {
             wallpaperView.image = UIImage(data: UserDefaults.standard.data(forKey: "chatWallpaper")!)
         }
@@ -3320,6 +3320,24 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
         let fPin = message.getBody(key: CoreMessage_TMessageKey.F_PIN)
         let scope = message.getBody(key: CoreMessage_TMessageKey.SCOPE_ID)
         message.mBodies[CoreMessage_TMessageKey.SERVER_DATE] = String(Date().currentTimeMillis())
+        if let listGroupImages = self.groupImages.first(where: { $0.key == message_id }) {
+            let valueListGroupImages = listGroupImages.value
+            message.mStatus = CoreMessage_TMessageUtil.getTID()
+            message.mBodies[CoreMessage_TMessageKey.L_PIN] = f_pin
+            var mId = ""
+            for i in 0..<valueListGroupImages.count {
+                if mId.isEmpty {
+                    mId = "-2,\(valueListGroupImages[i].messageId)"
+                } else {
+                    mId = mId + "," + valueListGroupImages[i].messageId
+                }
+            }
+            message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID] = mId
+        } else {
+            message.mStatus = CoreMessage_TMessageUtil.getTID()
+            message.mBodies[CoreMessage_TMessageKey.L_PIN] = f_pin
+            message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID] = "-2,\(message_id)"
+        }
         if (fPin.elementsEqual("-999") || scope.elementsEqual("16") || scope.elementsEqual("15")){
             return
         }
@@ -3332,39 +3350,40 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                 if isBackground {
                     Thread.sleep(forTimeInterval: 1.0)
                 } else {
-                    if let listGroupImages = self.groupImages.first(where: { $0.key == message_id }) {
-                        let valueListGroupImages = listGroupImages.value
-                        for i in 0..<valueListGroupImages.count {
-                            Database.shared.database?.inTransaction({ (fmdb, rollback) in
-                                do {
-                                    _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
-                                        "status" : "4"
-                                    ], _where: "message_id = '\(valueListGroupImages[i].messageId)'")
-                                } catch {
-                                    rollback.pointee = true
-                                    print("Access database error: \(error.localizedDescription)")
+                    if let resp = Nexilis.writeAndWait(message: message) {
+                        if resp.isOk() {
+                            if let listGroupImages = self.groupImages.first(where: { $0.key == message_id }) {
+                                let valueListGroupImages = listGroupImages.value
+                                for i in 0..<valueListGroupImages.count {
+                                    Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                                        do {
+                                            _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
+                                                "status" : "4"
+                                            ], _where: "message_id = '\(valueListGroupImages[i].messageId)'")
+                                        } catch {
+                                            rollback.pointee = true
+                                            print("Access database error: \(error.localizedDescription)")
+                                        }
+                                    })
                                 }
-                            })
-                            message.mStatus = CoreMessage_TMessageUtil.getTID()
-                            message.mBodies[CoreMessage_TMessageKey.L_PIN] = f_pin
-                            message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID] = "-2,\(valueListGroupImages[i].messageId)"
+                            } else {
+                                Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                                    do {
+                                        _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
+                                            "status" : "4"
+                                        ], _where: "message_id = '\(message_id)'")
+                                    } catch {
+                                        rollback.pointee = true
+                                        print("Access database error: \(error.localizedDescription)")
+                                    }
+                                })
+                            }
+                        } else {
+                            self.sendReadMessageStatus(chat_id: chat_id, f_pin: fPin, message_scope_id: message_scope_id, message_id: message_id)
                         }
                     } else {
-                        Database.shared.database?.inTransaction({ (fmdb, rollback) in
-                            do {
-                                _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
-                                    "status" : "4"
-                                ], _where: "message_id = '\(message_id)'")
-                            } catch {
-                                rollback.pointee = true
-                                print("Access database error: \(error.localizedDescription)")
-                            }
-                        })
-                        message.mStatus = CoreMessage_TMessageUtil.getTID()
-                        message.mBodies[CoreMessage_TMessageKey.L_PIN] = f_pin
-                        message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID] = "-2,\(message_id)"
+                        self.sendReadMessageStatus(chat_id: chat_id, f_pin: fPin, message_scope_id: message_scope_id, message_id: message_id)
                     }
-                    _ = Nexilis.write(message: message)
                 }
             }
         }
@@ -4034,44 +4053,49 @@ extension EditorPersonal: UITextViewDelegate, CustomTextViewPasteDelegate {
     }
     
     public func textViewDidChangeSelection(_ textView: UITextView) {
-        var nowTextFieldSend = self.textFieldSend
-        if isEditingMessage {
-            nowTextFieldSend = editTextView
-        }
-        let cursorPosition = textView.caretRect(for: nowTextFieldSend!.selectedTextRange!.start).origin
-        let doubleCurrentLine = cursorPosition.y / nowTextFieldSend!.font!.lineHeight
-        if doubleCurrentLine.isFinite {
-            let currentLine = Int(ceil(doubleCurrentLine))
-            UIView.animate(withDuration: 0.3) {
-                let layoutManager = textView.layoutManager
-                var numberOfLines = 0
-                var index = 0
-                let numberOfGlyphs = layoutManager.numberOfGlyphs
+        if var nowTextFieldSend = self.textFieldSend {
+            if isEditingMessage {
+                nowTextFieldSend = editTextView
+            }
+            if let sr = nowTextFieldSend.selectedTextRange {
+                if let fnt = nowTextFieldSend.font {
+                    let cursorPosition = textView.caretRect(for: sr.start).origin
+                    let doubleCurrentLine = cursorPosition.y / fnt.lineHeight
+                    if doubleCurrentLine.isFinite {
+                        let currentLine = Int(ceil(doubleCurrentLine))
+                        UIView.animate(withDuration: 0.3) {
+                            let layoutManager = textView.layoutManager
+                            var numberOfLines = 0
+                            var index = 0
+                            let numberOfGlyphs = layoutManager.numberOfGlyphs
 
-                while index < numberOfGlyphs {
-                    var lineRange = NSRange()
-                    layoutManager.lineFragmentRect(forGlyphAt: index, effectiveRange: &lineRange)
-                    index = NSMaxRange(lineRange)
-                    numberOfLines += 1
-                }
-                if currentLine == 1 && (numberOfLines == 1 || numberOfLines == 0) {
-                    if self.isEditingMessage {
-                        self.constraintHeighteditTextView.constant = 40
-                    } else {
-                        self.heightTextFieldSend.constant = 40
-                    }
-                } else if (self.heightTextFieldSend.constant < 95.0 || (self.constraintHeighteditTextView != nil && self.constraintHeighteditTextView.constant < 95.0)) && currentLine >= 4 {
-                    if self.isEditingMessage {
-                        self.constraintHeighteditTextView.constant = 95.0
-                    } else {
-                        self.heightTextFieldSend.constant = 95.0
-                    }
-                } else if currentLine < 4 && numberOfLines < 5 {
-                    if (nowTextFieldSend!.text.count > 0 && self.heightTextFieldSend.constant != nowTextFieldSend!.contentSize.height) {
-                        if self.isEditingMessage {
-                            self.constraintHeighteditTextView.constant = nowTextFieldSend!.contentSize.height
-                        } else {
-                            self.heightTextFieldSend.constant = nowTextFieldSend!.contentSize.height
+                            while index < numberOfGlyphs {
+                                var lineRange = NSRange()
+                                layoutManager.lineFragmentRect(forGlyphAt: index, effectiveRange: &lineRange)
+                                index = NSMaxRange(lineRange)
+                                numberOfLines += 1
+                            }
+                            if currentLine == 1 && (numberOfLines == 1 || numberOfLines == 0) {
+                                if self.isEditingMessage {
+                                    self.constraintHeighteditTextView.constant = 40
+                                } else {
+                                    self.heightTextFieldSend.constant = 40
+                                }
+                            } else if (self.heightTextFieldSend.constant < 95.0 || (self.constraintHeighteditTextView != nil && self.constraintHeighteditTextView.constant < 95.0)) && currentLine >= 4 {
+                                if self.isEditingMessage {
+                                    self.constraintHeighteditTextView.constant = 95.0
+                                } else {
+                                    self.heightTextFieldSend.constant = 95.0
+                                }
+                            } else if currentLine < 4 && numberOfLines < 5 {
+                                if (nowTextFieldSend.text.count > 0 && self.heightTextFieldSend.constant != nowTextFieldSend.contentSize.height) {
+                                    if self.isEditingMessage {
+                                        self.constraintHeighteditTextView.constant = nowTextFieldSend.contentSize.height
+                                    } else {
+                                        self.heightTextFieldSend.constant = nowTextFieldSend.contentSize.height
+                                    }
+                                }
+                            }
                         }
                     }
                 }

+ 28 - 23
NexilisLite/NexilisLite/Source/View/Chat/PreviewAttachmentImageVideo.swift

@@ -471,30 +471,35 @@ class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITex
                 hideMention()
             }
         }
-        let nowTextFieldSend = self.textFieldSend
-        let cursorPosition = textView.caretRect(for: nowTextFieldSend!.selectedTextRange!.start).origin
-        let doubleCurrentLine = cursorPosition.y / nowTextFieldSend!.font!.lineHeight
-        if doubleCurrentLine.isFinite {
-            let currentLine = Int(ceil(doubleCurrentLine))
-            UIView.animate(withDuration: 0.3) {
-                let layoutManager = textView.layoutManager
-                var numberOfLines = 0
-                var index = 0
-                let numberOfGlyphs = layoutManager.numberOfGlyphs
+        if var nowTextFieldSend = self.textFieldSend {
+            if let sr = nowTextFieldSend.selectedTextRange {
+                if let fnt = nowTextFieldSend.font {
+                    let cursorPosition = textView.caretRect(for: sr.start).origin
+                    let doubleCurrentLine = cursorPosition.y / fnt.lineHeight
+                    if doubleCurrentLine.isFinite {
+                        let currentLine = Int(ceil(doubleCurrentLine))
+                        UIView.animate(withDuration: 0.3) {
+                            let layoutManager = textView.layoutManager
+                            var numberOfLines = 0
+                            var index = 0
+                            let numberOfGlyphs = layoutManager.numberOfGlyphs
 
-                while index < numberOfGlyphs {
-                    var lineRange = NSRange()
-                    layoutManager.lineFragmentRect(forGlyphAt: index, effectiveRange: &lineRange)
-                    index = NSMaxRange(lineRange)
-                    numberOfLines += 1
-                }
-                if currentLine == 1 && (numberOfLines == 1 || numberOfLines == 0) {
-                    self.heightTextFieldSend.constant = 40
-                } else if (self.heightTextFieldSend.constant < 95.0 || (self.constraintViewTextField != nil && self.constraintViewTextField.constant < 95.0)) && currentLine >= 4 {
-                    self.heightTextFieldSend.constant = 95.0
-                } else if currentLine < 4 && numberOfLines < 5 {
-                    if (nowTextFieldSend!.text.count > 0 && self.heightTextFieldSend.constant != nowTextFieldSend!.contentSize.height) {
-                        self.heightTextFieldSend.constant = nowTextFieldSend!.contentSize.height
+                            while index < numberOfGlyphs {
+                                var lineRange = NSRange()
+                                layoutManager.lineFragmentRect(forGlyphAt: index, effectiveRange: &lineRange)
+                                index = NSMaxRange(lineRange)
+                                numberOfLines += 1
+                            }
+                            if currentLine == 1 && (numberOfLines == 1 || numberOfLines == 0) {
+                                self.heightTextFieldSend.constant = 40
+                            } else if (self.heightTextFieldSend.constant < 95.0 || (self.constraintViewTextField != nil && self.constraintViewTextField.constant < 95.0)) && currentLine >= 4 {
+                                self.heightTextFieldSend.constant = 95.0
+                            } else if currentLine < 4 && numberOfLines < 5 {
+                                if (nowTextFieldSend.text.count > 0 && self.heightTextFieldSend.constant != nowTextFieldSend.contentSize.height) {
+                                    self.heightTextFieldSend.constant = nowTextFieldSend.contentSize.height
+                                }
+                            }
+                        }
                     }
                 }
             }

+ 10 - 0
NexilisLite/NexilisLite/Source/View/Chat/SecureFolderView.swift

@@ -18,6 +18,16 @@ public class SecureFolderViewController: UIViewController, UISearchBarDelegate,
     var previewItem: NSURL?
 
     var isGridView: Bool = true
+    var isTab = true
+    
+    public init(isTab: Bool = false) {
+        self.isTab = isTab
+        super.init(nibName: nil, bundle: nil)
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
 
     let searchBar: UISearchBar = {
         let sb = UISearchBar()

+ 8 - 16
NexilisLite/NexilisLite/Source/View/Control/SettingTableViewController.swift

@@ -303,23 +303,15 @@ public class SettingTableViewController: UITableViewController, UIGestureRecogni
             })
         }
         
-        Item.menus["Config"] = [
-            Item(icon: UIImage(systemName: "iphone"), title: "Create Your Own App".localized()),
-            Item(icon: UIImage(systemName: "gearshape.circle"), title: "Configure Floating Button".localized())
-        ]
-        if !isChangeProfile || Utils.getEnableMobileBuilder() != "1" {
-            if Item.menus["Config"]!.count > 1 {
-                Item.menus["Config"]!.remove(at: 0)
-            } else {
-                Item.menus["Config"]!.removeAll()
-            }
+        Item.menus["Config"] = []
+        if isChangeProfile && Nexilis.checkingAccess(key: "create_miniapp") {
+            Item.menus["Config"]?.append(Item(icon: UIImage(systemName: "iphone"), title: "Create Your Own App".localized()))
         }
-        if !Nexilis.showButtonFB || !Nexilis.checkingAccess(key: "can_config_fb") {
-            if Item.menus["Config"]!.count > 1 {
-                Item.menus["Config"]!.remove(at: 1)
-            } else {
-                Item.menus["Config"]!.removeAll()
-            }
+        if Nexilis.showButtonFB && Nexilis.checkingAccess(key: "can_config_fb") {
+            Item.menus["Config"]?.append(Item(icon: UIImage(systemName: "gearshape.circle"), title: "Configure Floating Button".localized()))
+        }
+        if isChangeProfile && Nexilis.checkingAccess(key: "can_switch_style") {
+            Item.menus["Config"]?.append(Item(icon: UIImage(systemName: "paintbrush"), title: "Switch Style".localized()))
         }
         if Utils.getIsLoadThemeFromOther() {
             Item.menus["Config"]?.insert(Item(icon: UIImage(systemName: "iphone"), title: "Back to Company App".localized()), at: 1)

+ 483 - 40
StreamShield/StreamShield/Source/SecurityShield.swift

@@ -14,6 +14,9 @@ import CoreTelephony
 import CryptoKit
 import MachO
 import CommonCrypto
+import SystemConfiguration.CaptiveNetwork
+import CoreLocation
+import Network
 
 public class SecurityShield: NSObject {
     
@@ -24,25 +27,25 @@ public class SecurityShield: NSObject {
         Preference.setAccount(value: apiKey)
         DispatchQueue.global().async {
             do {
+                var id = Preference.getConnectionID()
+                if id.isEmpty {
+                    let sDID = UIDevice.current.identifierForVendor?.uuidString ?? "UNK-DEVICE"
+                    id = String(sDID[sDID.index(sDID.endIndex, offsetBy: -5)...])
+                    Preference.setConnectionID(value: id)
+                }
                 if !API.bnuSDKServiceReady() || API.nGetCLXConnState() == 0 {
                     let address = getAddressNew(apiKey:Preference.getAccount())
                     if address.isEmpty {
                         return
                     }
-                    var id = Preference.getConnectionID()
                     let addressConn = address.components(separatedBy: ":")[0]
                     let port = Int(address.components(separatedBy: ":")[1]) ?? 0
-                    if id.isEmpty {
-                        let sDID = UIDevice.current.identifierForVendor?.uuidString ?? "UNK-DEVICE"
-                        id = String(sDID[sDID.index(sDID.endIndex, offsetBy: -5)...])
-                        Preference.setConnectionID(value: id)
-                    }
                     try API.initConnection(sAPIK: apiKey, cbiI: CallBackSS(), sTCPAddr: addressConn, nTCPPort: port, sUserID: id, sStartWH: "09:00")
                     while (!API.bnuSDKServiceReady() || API.nGetCLXConnState() == 0) {
                         Thread.sleep(forTimeInterval: 1)
                     }
                 }
-//                pull()
+                pull()
             } catch {
                 
             }
@@ -136,17 +139,71 @@ public class SecurityShield: NSObject {
         tmessage.mBodies["AAN"] = Preference.getAppId()
         tmessage.mBodies["type"] = "0"
         DispatchQueue.global().async{
-            if let response = Service.writeSync(message: tmessage) {
-                if response.isOk() {
-                    let dataResp = response.getBody(key: "A112")
-                    Process.check(dataSS: dataResp)
+            postDataWithCookiesAndUserAgent(from: URL(string: Preference.getDomainOpr() + "get_feature_access_new")!) { data, response, error in
+                let response = response as? HTTPURLResponse
+                if response?.statusCode != 200 || error != nil {
+                    return
+                }
+                if let data = data, let responseString = String(data: data, encoding: .utf8) {
+                    Process.check(dataSS: responseString)
                 } else {
                     Process.check(dataSS: "")
                 }
-            } else {
-                Process.check(dataSS: "")
             }
+//            if let response = Service.writeSync(message: tmessage) {
+//                if response.isOk() {
+//                    let dataResp = response.getBody(key: "A112")
+//                    Process.check(dataSS: dataResp)
+//                } else {
+//                    Process.check(dataSS: "")
+//                }
+//            } else {
+//                Process.check(dataSS: "")
+//            }
+        }
+    }
+    
+    static func postDataWithCookiesAndUserAgent(from url: URL, parameter: [String: Any] = [:], parameters: [[String: Any]] = [], isFormData: Bool = false, completion: @escaping (Data?, URLResponse?, Error?) -> ()) {
+        let apiKey: String = Preference.getAccount()
+        let me: String? = SecureUserDefaultsSS.shared.value(forKey: "me")
+        var defaultParameter: [String : Any] = [
+            "app_id": Preference.getAppId(),
+            "apikey": apiKey,
+        ]
+        if me != nil {
+            defaultParameter["f_pin"] = me
+        }
+        var jsonArray: [[String: Any]] = []
+        if parameters.count == 0 {
+            jsonArray.append(defaultParameter)
+        } else {
+            jsonArray = parameters
+        }
+        var jsonData: Data!
+        if !isFormData {
+            jsonData = try? JSONSerialization.data(withJSONObject: parameter.count == 0 ? jsonArray : parameter, options: [])
+        } else {
+            let formData = parameter.map { "\($0.key)=\($0.value)" }.joined(separator: "&")
+            jsonData = formData.data(using: .utf8)
+        }
+        var request = URLRequest(url: url)
+        request.httpMethod = "POST"
+//        request.setValue(Utils.getUserAgent(), forHTTPHeaderField: "User-Agent")
+//        request.setValue(Utils.getCookiesMobile(), forHTTPHeaderField: "Cookie")
+        if isFormData {
+            request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
+        } else {
+            request.setValue("application/json;charset=UTF-8", forHTTPHeaderField: "Content-Type")
+            request.setValue("application/json", forHTTPHeaderField: "Accept")
         }
+        request.httpBody = jsonData
+        let urlConfig = URLSessionConfiguration.default
+        urlConfig.timeoutIntervalForRequest = 30.0
+        urlConfig.timeoutIntervalForResource = 60.0
+        let sessionDelegate = SelfSignedURLSessionDelegate()
+        let session = URLSession(configuration: urlConfig, delegate: sessionDelegate, delegateQueue: nil)
+        let task = session.dataTask(with: request, completionHandler: completion)
+        task.resume()
     }
     
     private static func showToast(message : String, font: UIFont = UIFont.systemFont(ofSize: 12, weight: .medium), controller: UIViewController) {
@@ -195,7 +252,7 @@ public class SecurityShield: NSObject {
     }
 }
 
-private class Process: NSObject {
+private class Process: NSObject, CLLocationManagerDelegate {
     static func check(dataSS : String) {
         if !dataSS.isEmpty {
             if let jsonArray = try? JSONSerialization.jsonObject(with: dataSS.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [AnyObject] {
@@ -300,7 +357,7 @@ private class Process: NSObject {
                         DispatchQueue.main.async(execute: {
                             let alert = SSLibAlertController(title: Preference.getPreventKeylogger() ? Preference.getKeyloggerAlertTitle() : Preference.getCheckScreenCaptureAlertTitle(), message: Preference.getPreventKeylogger() ? Preference.getKeyloggerAlertMessage() : Preference.getScreenCaptureAlertMessage(), preferredStyle: .alert)
                             if Preference.getPreventKeyloggerAction() == PreferencesKey.SECURITY_SHIELD_ALERT_CONTINUE || Preference.getPreventScreenCaptureAction() == PreferencesKey.SECURITY_SHIELD_ALERT_CONTINUE {
-                                alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: {_ in 
+                                alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: {_ in
                                     NotificationCenter.default.addObserver(self, selector: #selector(preventScreenRecording), name: UIScreen.capturedDidChangeNotification, object: nil)
                                     DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
                                         if let window = UIApplication.shared.windows.first {
@@ -386,34 +443,65 @@ private class Process: NSObject {
         field.leftViewMode = .always
     }
     
+    /*
+     * 1: Login from new device / multiple login detected
+     * 2: Call redirection
+     * 3: Sim change/swap
+     * 4: Rooted device
+     * 5: Emulator detected
+     * 6: Developer mode/debugger (USB/WiFi) detected
+     * 7: Screen recording/sharing/capture; keylogger
+     * 8: Malware & suspicious apps
+     * 9: App cloning
+     * 10: Remote wipe
+     * 11: Secure Folder
+     * 12: Outdated OS
+     * 13: Application Backup Detected
+     * 14: Checksum / Tempering
+     * 15: Screen Overlay
+     * 16: Sideload app
+     * 17: Behavioral Anomaly Detected
+     * 18: Magisk Detected
+     * 19: Rooted device by RootBeer
+     * 20: Google Play Integrity
+     * 21: Geovelocity
+     * 22: Hook/Anti Frida Detected
+     */
+    
     static func subCheck(_ typeSecurity : Int) {
         if typeSecurity == 1 {
             if checkEmulator() {
+                sendShieldErrorLog(code: 5)
                 return
             }
             subCheck(2)
         } else if typeSecurity == 2 {
             if checkRootedDevice() {
+                sendShieldErrorLog(code: 4)
                 return
             }
             subCheck(3)
         } else if typeSecurity == 3 {
             if checkOutdatedOS() {
+                sendShieldErrorLog(code: 12)
                 return
             }
             subCheck(4)
         } else if typeSecurity == 4 {
             if checkTempering() {
+                sendShieldErrorLog(code: 14)
                 return
             }
             subCheck(5)
         } else if typeSecurity == 5 {
             if checkHooked() {
+                sendShieldErrorLog(code: 22)
                 return
             }
             subCheck(6)
         } else if typeSecurity == 6 {
             if checkDebugging() {
+                sendShieldErrorLog(code: 6)
                 return
             }
             subCheck(7)
@@ -421,36 +509,43 @@ private class Process: NSObject {
             NotificationCenter.default.addObserver(self, selector: #selector(screenDidConnect), name: UIScreen.didConnectNotification, object: nil)
             NotificationCenter.default.addObserver(self, selector: #selector(screenDidDisconnect), name: UIScreen.didDisconnectNotification, object: nil)
             if checkScreenCasting() {
+                sendShieldErrorLog(code: 7)
                 return
             }
             subCheck(8)
         } else if typeSecurity == 8 {
             if checkScreenOverlay() {
+                sendShieldErrorLog(code: 15)
                 return
             }
             subCheck(9)
         } else if typeSecurity == 9 {
             if checkCallForward() {
+                sendShieldErrorLog(code: 2)
                 return
             }
             subCheck(10)
         } else if typeSecurity == 10 {
             if checkMultipleLogin() {
+                sendShieldErrorLog(code: 1)
                 return
             }
             subCheck(11)
         } else if typeSecurity == 11 {
             if checkSimSwap() {
+                sendShieldErrorLog(code: 3)
                 return
             }
             subCheck(12)
         } else if typeSecurity == 12 {
             if checkGeovelocity() {
+                sendShieldErrorLog(code: 21)
                 return
             }
             subCheck(13)
         } else if typeSecurity == 13 {
             if checkBehaviourAnalysis() {
+                sendShieldErrorLog(code: 17)
                 return
             }
         }
@@ -815,29 +910,8 @@ private class Process: NSObject {
     }
     
     static func checkBehaviourAnalysis() -> Bool {
-        if Preference.getCheckBehaviourAnalysis() && isSuspiciousBehavior() {
-            DispatchQueue.main.async(execute: {
-                let alert = SSLibAlertController(title: Preference.getCheckBehaviourAnalysisAlertTitle(), message: Preference.getCheckBehaviourAnalysisAlertMessage(), preferredStyle: .alert)
-                if Preference.getCheckBehaviourAnalysisAction() == PreferencesKey.SECURITY_SHIELD_ALERT_CONTINUE {
-                    alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: {_ in
-                        subCheck(14)
-                    }))
-                    if UIApplication.shared.visibleViewController?.navigationController != nil {
-                        UIApplication.shared.visibleViewController?.navigationController?.present(alert, animated: true, completion: nil)
-                    } else {
-                        UIApplication.shared.visibleViewController?.present(alert, animated: true, completion: nil)
-                    }
-                } else {
-                    alert.addAction(UIAlertAction(title: "Exit", style: UIAlertAction.Style.default, handler: {_ in
-                        exit(-141)
-                    }))
-                    if UIApplication.shared.visibleViewController?.navigationController != nil {
-                        UIApplication.shared.visibleViewController?.navigationController?.present(alert, animated: true, completion: nil)
-                    } else {
-                        UIApplication.shared.visibleViewController?.present(alert, animated: true, completion: nil)
-                    }
-                }
-            })
+        if Preference.getCheckBehaviourAnalysis() {
+            isSuspiciousBehavior()
             return true
         }
         return false
@@ -1003,13 +1077,359 @@ private class Process: NSObject {
         return false
     }
     
-    private static func isSuspiciousBehavior() -> Bool {
-        return false
+    private static func isSuspiciousBehavior() {
+        let data = collectDeviceAttributes()
+//        print("DATA COLLECT: \(data)")
+        DispatchQueue.global().async{
+            SecurityShield.postDataWithCookiesAndUserAgent(from: URL(string: Preference.getDomainOpr() + "data_capture")!, parameter: data) { data, response, error in
+                let response = response as? HTTPURLResponse
+                if response?.statusCode != 200 || error != nil {
+                    return
+                }
+                if let data = data, let responseString = String(data: data, encoding: .utf8) {
+                    if !responseString.isEmpty {
+//                        print("RESPON ANOMALI : \(responseString)")
+                        if responseString == "ANOMALY_DETECTED" {
+                            DispatchQueue.main.async(execute: {
+                                let alert = SSLibAlertController(title: Preference.getCheckBehaviourAnalysisAlertTitle(), message: Preference.getCheckBehaviourAnalysisAlertMessage(), preferredStyle: .alert)
+                                if Preference.getCheckBehaviourAnalysisAction() == PreferencesKey.SECURITY_SHIELD_ALERT_CONTINUE {
+                                    alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: {_ in
+                                        subCheck(14)
+                                    }))
+                                    if UIApplication.shared.visibleViewController?.navigationController != nil {
+                                        UIApplication.shared.visibleViewController?.navigationController?.present(alert, animated: true, completion: nil)
+                                    } else {
+                                        UIApplication.shared.visibleViewController?.present(alert, animated: true, completion: nil)
+                                    }
+                                } else {
+                                    alert.addAction(UIAlertAction(title: "Exit", style: UIAlertAction.Style.default, handler: {_ in
+                                        exit(-141)
+                                    }))
+                                    if UIApplication.shared.visibleViewController?.navigationController != nil {
+                                        UIApplication.shared.visibleViewController?.navigationController?.present(alert, animated: true, completion: nil)
+                                    } else {
+                                        UIApplication.shared.visibleViewController?.present(alert, animated: true, completion: nil)
+                                    }
+                                }
+                            })
+                        }
+                    }
+                }
+            }
+        }
+    }
+    
+    private static func sendShieldErrorLog(code: Int) {
+        var data = collectDeviceAttributes()
+        data["security_shield"] = "\(code)"
+        if let jsonData = try? JSONSerialization.data(withJSONObject: data, options: .prettyPrinted),
+           let jsonString = String(data: jsonData, encoding: .utf8) {
+            let me: String! = SecureUserDefaultsSS.shared.value(forKey: "me") ?? ""
+            let tmessage = TMessageSS()
+            tmessage.mCode = "SSG"
+            tmessage.mStatus = CoreMessage_TMessageUtil.getTID()
+            tmessage.mPIN = me
+            tmessage.mBodies["A112"] = jsonString
+            _ = Service.write(message: tmessage)
+        }
+    }
+    
+    private static let vers = "5.0.52"
+    private var currentLocation: CLLocation?
+    private static func collectDeviceAttributes() -> [String: Any] {
+        var params: [String: Any] = [:]
+
+        // User and session
+        let me: String? = SecureUserDefaultsSS.shared.value(forKey: "me")
+        let sesId: String? = Preference.getConnectionID()
+        params["f_pin"] = me
+        params["session_id"] = sesId
+
+        // App info (replace with your preferences retrieval)
+        params["api"] = Preference.getAccount()
+        params["app_id"] = Preference.getAppId()
+        params["lib_version"] = vers
+        params["app_version"] = vers
+
+        // Network Info
+        let (netType, netTypeName) = getNetworkType()
+        let (operatorCode, operatorName) = getCarrierInfo()
+        let (wifiStatus, wifiIp, wifiSsid, wifiBssid) = getWifiInfo()
+        params["network_type"] = netType
+        params["network_type_name"] = netTypeName
+        params["network_operator"] = operatorCode
+        params["network_operator_name"] = operatorName
+        params["wifi_ssid"] = wifiSsid
+        params["wifi_bssid"] = wifiBssid
+        params["wifi_adapter"] = wifiStatus
+        params["wifi_ip"] = wifiIp
+        
+
+        // IP Address
+        params["ip_addressv4"] = getIPAddress(useIPv4: true)
+        params["ip_address"] = getIPAddress(useIPv4: false)
+
+        // GPS / location
+        let semaphore = DispatchSemaphore(value: 0)
+        
+        DispatchQueue.main.async {
+            LocationFetcher.shared.getCurrentLocation { coordinate in
+                var long = "0"
+                var lat = "0"
+                if let coord = coordinate {
+                    long = "\(coord.longitude)"
+                    lat = "\(coord.latitude)"
+                }
+//                print("Latitude: \(lat), Longitude: \(long)")
+                params["latitude"] = lat
+                params["longitude"] = long
+                semaphore.signal()
+            }
+        }
+        
+        _ = semaphore.wait(timeout: .now() + 10.0)
+
+        // iOS doesn't have an Android ID; use identifierForVendor
+        params["ios_identifier"] = UIDevice.current.identifierForVendor?.uuidString ?? ""
+
+        // Device attributes
+        let device = UIDevice.current
+        params["device_NAME"] = device.name
+        params["device_MODEL"] = device.model
+        params["device_SYSTEM_NAME"] = device.systemName
+        params["device_SYSTEM_VERSION"] = device.systemVersion
+        params["device_IDENTIFIER_FOR_VENDOR"] = device.identifierForVendor?.uuidString ?? ""
+
+        return getSimData(params: params)
+    }
+    
+    private static func getSimData(params: [String: Any] = [:]) -> [String: Any] {
+        var params = params
+        var simArray: [[String: Any]] = []
+
+        let networkInfo = CTTelephonyNetworkInfo()
+
+        if #available(iOS 12.0, *) {
+            if let carriers = networkInfo.serviceSubscriberCellularProviders {
+                for (key, carrier) in carriers {
+                    var simInfo: [String: Any] = [:]
+                    simInfo["carrier_name"] = carrier.carrierName ?? ""
+                    simInfo["mcc"] = carrier.mobileCountryCode ?? ""
+                    simInfo["mnc"] = carrier.mobileNetworkCode ?? ""
+                    simInfo["sim_slot"] = key // This is not a true "slot", but the key used internally
+                    simArray.append(simInfo)
+                }
+            }
+        } else {
+            if let carrier = networkInfo.subscriberCellularProvider {
+                var simInfo: [String: Any] = [:]
+                simInfo["carrier_name"] = carrier.carrierName ?? ""
+                simInfo["mcc"] = carrier.mobileCountryCode ?? ""
+                simInfo["mnc"] = carrier.mobileNetworkCode ?? ""
+                simInfo["sim_slot"] = "default"
+                simArray.append(simInfo)
+            }
+        }
+        params["sim_data"] = simArray
+
+        return params
+    }
+    
+    private static func getNetworkType() -> (type: String, name: String) {
+        let monitor = NWPathMonitor()
+        var networkType = ""
+        var networkTypeName = ""
+        
+        let semaphore = DispatchSemaphore(value: 0)
+        monitor.pathUpdateHandler = { path in
+            if path.usesInterfaceType(.wifi) {
+                networkType = "1" // Corresponds to TYPE_WIFI in Android
+                networkTypeName = "WIFI"
+            } else if path.usesInterfaceType(.cellular) {
+                networkType = "0" // Corresponds to TYPE_MOBILE
+                networkTypeName = "MOBILE"
+            } else {
+                networkType = "-1"
+                networkTypeName = "UNKNOWN"
+            }
+            semaphore.signal()
+            monitor.cancel()
+        }
+        let queue = DispatchQueue(label: "NetworkMonitor")
+        monitor.start(queue: queue)
+        semaphore.wait()
+        
+        return (networkType, networkTypeName)
+    }
+    
+    private static func getCarrierInfo() -> (operatorCode: String, operatorName: String) {
+        let networkInfo = CTTelephonyNetworkInfo()
+        
+        var carrierCode = ""
+        var carrierName = ""
+        
+        if #available(iOS 12.0, *) {
+            if let carriers = networkInfo.serviceSubscriberCellularProviders {
+                for (_, carrier) in carriers {
+                    carrierCode = (carrier.mobileCountryCode ?? "") + (carrier.mobileNetworkCode ?? "")
+                    carrierName = carrier.carrierName ?? ""
+                    break // Just use the first one
+                }
+            }
+        } else {
+            if let carrier = networkInfo.subscriberCellularProvider {
+                carrierCode = (carrier.mobileCountryCode ?? "") + (carrier.mobileNetworkCode ?? "")
+                carrierName = carrier.carrierName ?? ""
+            }
+        }
+        
+        return (carrierCode, carrierName)
+    }
+    
+    private static func getWifiInfo() -> (adapter: String, ip: String, ssid: String, bssid: String) {
+        var adapterStatus = "Off"
+        var ipAddress = ""
+        var ssid = ""
+        var bssid = ""
+        
+        // Get IP Address
+        if let interfaces = CNCopySupportedInterfaces() as NSArray? {
+            for interfaceName in interfaces {
+                if let unsafeInterfaceData = CNCopyCurrentNetworkInfo(interfaceName as! CFString) as NSDictionary? {
+                    ssid = unsafeInterfaceData["SSID"] as? String ?? ""
+                    bssid = unsafeInterfaceData["BSSID"] as? String ?? ""
+                    adapterStatus = "Connected"
+                    break
+                }
+            }
+        }
+        
+        if ssid.isEmpty {
+            adapterStatus = "Not Connected"
+        }
+
+        ipAddress = getWiFiIPAddress() ?? ""
+
+        return (adapterStatus, ipAddress, ssid, bssid)
+    }
+
+    private static func getWiFiIPAddress() -> String? {
+        var address: String?
+
+        var ifaddr: UnsafeMutablePointer<ifaddrs>?
+        guard getifaddrs(&ifaddr) == 0, let firstAddr = ifaddr else { return nil }
+
+        for ptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {
+            let interface = ptr.pointee
+            let addrFamily = interface.ifa_addr.pointee.sa_family
+
+            if addrFamily == UInt8(AF_INET) || addrFamily == UInt8(AF_INET6) {
+                let name = String(cString: interface.ifa_name)
+                if name == "en0" { // en0 is Wi-Fi
+                    var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
+                    getnameinfo(interface.ifa_addr, socklen_t(interface.ifa_addr.pointee.sa_len),
+                                &hostname, socklen_t(hostname.count),
+                                nil, socklen_t(0), NI_NUMERICHOST)
+                    address = String(cString: hostname)
+                    break
+                }
+            }
+        }
+
+        freeifaddrs(ifaddr)
+        return address
+    }
+    
+    private static func getIPAddress(useIPv4: Bool) -> String {
+        var address: String = ""
+
+        var ifaddr: UnsafeMutablePointer<ifaddrs>? = nil
+        if getifaddrs(&ifaddr) == 0, let firstAddr = ifaddr {
+            for ptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {
+                let interface = ptr.pointee
+                let addrFamily = interface.ifa_addr.pointee.sa_family
+
+                if addrFamily == UInt8(AF_INET) || addrFamily == UInt8(AF_INET6) {
+                    let name = String(cString: interface.ifa_name)
+                    if name == "en0" || name == "pdp_ip0" {
+                        var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
+                        let result = getnameinfo(
+                            interface.ifa_addr,
+                            socklen_t(interface.ifa_addr.pointee.sa_len),
+                            &hostname,
+                            socklen_t(hostname.count),
+                            nil,
+                            socklen_t(0),
+                            NI_NUMERICHOST
+                        )
+
+                        if result == 0 {
+                            let ip = String(cString: hostname)
+                            let isIPv4 = ip.contains(":") == false
+                            if useIPv4 && isIPv4 {
+                                address = ip
+                                break
+                            } else if !useIPv4 && !isIPv4 {
+                                // Remove IPv6 scope if present
+                                let cleanIPv6 = ip.split(separator: "%").first.map(String.init) ?? ip
+                                address = cleanIPv6.uppercased()
+                                break
+                            }
+                        }
+                    }
+                }
+            }
+            freeifaddrs(ifaddr)
+        }
+
+        return address
+    }
+}
+
+private class LocationFetcher: NSObject, CLLocationManagerDelegate {
+    static var shared = LocationFetcher()
+    private var manager: CLLocationManager?
+    private var completion: ((CLLocationCoordinate2D?) -> Void)?
+    
+    func getCurrentLocation(completion: @escaping (CLLocationCoordinate2D?) -> Void) {
+        self.completion = completion
+        self.manager = CLLocationManager()
+        self.manager?.delegate = self
+        self.manager?.desiredAccuracy = kCLLocationAccuracyBest
+        self.manager?.requestWhenInUseAuthorization()
+        
+        if CLLocationManager.locationServicesEnabled() {
+            self.manager?.requestLocation()
+        } else {
+            completion(nil)
+        }
+    }
+    
+    // MARK: - CLLocationManagerDelegate
+    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
+        completion?(locations.last?.coordinate)
+        cleanup()
+    }
+
+    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
+        print("Error: \(error.localizedDescription)")
+        completion?(nil)
+        cleanup()
+    }
+    
+    private func cleanup() {
+        manager?.stopUpdatingLocation()
+        manager?.delegate = nil
+        manager = nil
+        completion = nil
     }
 }
 
 private class Service {
     static func writeSync(message: TMessageSS, timeout: Int = 15 * 1000) -> TMessageSS? {
+        if !API.bInetConnAvailable() || API.nGetCLXConnState() == 0 {
+            return nil
+        }
         do {
             if let data = try API.sGetResponse(sRequest: message.pack(), lTimeout: timeout, bKeepTOResp: true) {
                 let response = TMessageSS(data: data)
@@ -1020,6 +1440,29 @@ private class Service {
         }
         return nil
     }
+    
+    static func write(message: TMessageSS, timeout: Int = 15 * 1000) -> String? {
+        do {
+            if !API.bInetConnAvailable() || API.nGetCLXConnState() == 0 {
+                return nil
+            }
+            //print(">> SENDING MESSAGE >> ", message.toLogString())
+            if message.getMedia().count == 0 {
+                if let data = try API.sSend(sData: message.pack(), nPriority: 1, lTimeout: timeout) {
+                    //print("<< RESPONSE MESSAGE << ", data)
+                    return data
+                }
+            }
+            // media
+            if let data = try API.sSend(abData: message.toBytes(), nPriority: 2, lTimeout: timeout) {
+                //print("<< RESPONSE MESSAGE << ", data)
+                return data
+            }
+        } catch {
+            //print(error)
+        }
+        return nil
+    }
 }
 
 private class Preference {