Browse Source

update and fix bugs for 5.0.39

alqindiirsyam 2 tháng trước cách đây
mục cha
commit
6f92cd0d50
34 tập tin đã thay đổi với 498 bổ sung236 xóa
  1. BIN
      AppBuilder/.DS_Store
  2. 4 4
      AppBuilder/AppBuilder.xcodeproj/project.pbxproj
  3. 8 1
      AppBuilder/AppBuilder/FirstTabViewController.swift
  4. 4 4
      AppBuilder/AppBuilder/FourthTabViewController.swift
  5. 1 1
      AppBuilder/AppBuilder/SceneDelegate.swift
  6. 21 6
      AppBuilder/AppBuilder/SecondTabViewController.swift
  7. 8 0
      AppBuilder/AppBuilder/ThirdTabViewController.swift
  8. 1 1
      NexilisLite/NexilisLite.podspec
  9. BIN
      NexilisLite/NexilisLite/.DS_Store
  10. 2 0
      NexilisLite/NexilisLite/Resource/id.lproj/Localizable.strings
  11. 36 2
      NexilisLite/NexilisLite/Source/APIS.swift
  12. 22 1
      NexilisLite/NexilisLite/Source/Callback.swift
  13. 14 0
      NexilisLite/NexilisLite/Source/CoreMessage_TMessageBank.swift
  14. 1 0
      NexilisLite/NexilisLite/Source/CoreMessage_TMessageCode.swift
  15. 57 32
      NexilisLite/NexilisLite/Source/Database.swift
  16. 4 3
      NexilisLite/NexilisLite/Source/Download.swift
  17. 14 24
      NexilisLite/NexilisLite/Source/Extension.swift
  18. 31 19
      NexilisLite/NexilisLite/Source/FileEncryption.swift
  19. 13 4
      NexilisLite/NexilisLite/Source/Network.swift
  20. 107 49
      NexilisLite/NexilisLite/Source/Nexilis.swift
  21. 13 2
      NexilisLite/NexilisLite/Source/Utils.swift
  22. 1 1
      NexilisLite/NexilisLite/Source/View/Call/QmeraAudioViewController.swift
  23. 2 2
      NexilisLite/NexilisLite/Source/View/Call/QmeraVideoViewController.swift
  24. 1 1
      NexilisLite/NexilisLite/Source/View/Call/VideoConferenceViewController.swift
  25. 1 1
      NexilisLite/NexilisLite/Source/View/Chat/ChatGPTBotView.swift
  26. 35 17
      NexilisLite/NexilisLite/Source/View/Chat/EditorGroup.swift
  27. 33 19
      NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift
  28. 5 5
      NexilisLite/NexilisLite/Source/View/Chat/EditorStarMessages.swift
  29. 5 5
      NexilisLite/NexilisLite/Source/View/Chat/MessageInfo.swift
  30. 16 6
      NexilisLite/NexilisLite/Source/View/Control/BackupRestoreView.swift
  31. 31 15
      NexilisLite/NexilisLite/Source/View/Control/ContactChatViewController.swift
  32. 1 1
      NexilisLite/NexilisLite/Source/View/Control/HistoryCCViewController.swift
  33. 5 9
      NexilisLite/NexilisLite/Source/View/Control/SettingTableViewController.swift
  34. 1 1
      NexilisLite/Podfile

BIN
AppBuilder/.DS_Store


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

@@ -560,7 +560,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 5.0.35;
+				MARKETING_VERSION = 5.0.39;
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
@@ -596,7 +596,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 5.0.35;
+				MARKETING_VERSION = 5.0.39;
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
@@ -632,7 +632,7 @@
 					"@executable_path/../../Frameworks",
 				);
 				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
-				MARKETING_VERSION = 5.0.35;
+				MARKETING_VERSION = 5.0.39;
 				OTHER_CFLAGS = "-fstack-protector-strong";
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder.AppBuilderShare;
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -671,7 +671,7 @@
 					"@executable_path/../../Frameworks",
 				);
 				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
-				MARKETING_VERSION = 5.0.35;
+				MARKETING_VERSION = 5.0.39;
 				OTHER_CFLAGS = "-fstack-protector-strong";
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder.AppBuilderShare;
 				PRODUCT_NAME = "$(TARGET_NAME)";

+ 8 - 1
AppBuilder/AppBuilder/FirstTabViewController.swift

@@ -105,7 +105,7 @@ class FirstTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
         
         NotificationCenter.default.addObserver(self, selector: #selector(onShowAC(notification:)), name: NSNotification.Name(rawValue: "onShowAC"), object: nil)
         NotificationCenter.default.addObserver(self, selector: #selector(onRefreshWebView(notification:)), name: NSNotification.Name(rawValue: "onRefreshWebView"), object: nil)
-//        NotificationCenter.default.addObserver(self, selector: #selector(onRefreshWebView(notification:)), name: UIApplication.willEnterForegroundNotification, object: nil)
+        NotificationCenter.default.addObserver(self, selector: #selector(onResumeWebView(notification:)), name: UIApplication.willEnterForegroundNotification, object: nil)
         FirstTabViewController.canLoadURL = true
         processURL()
     }
@@ -242,6 +242,10 @@ class FirstTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
         FirstTabViewController.forceRefresh = true
     }
     
+    @objc func onResumeWebView(notification: NSNotification) {
+        Nexilis.reloadCookies(webView: webView)
+    }
+    
     override func viewWillDisappear(_ animated: Bool) {
         if self.webView != nil {
             if self.webView.scrollView.contentOffset.y < 0 { // Move tableView to top
@@ -279,6 +283,9 @@ class FirstTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
     }
     
     func hideTabBar() {
+        if UIApplication.shared.windows.first == nil {
+            return
+        }
         var viewController = UIApplication.shared.windows.first!.rootViewController
         if !(viewController is ViewController) {
             viewController = self.parent

+ 4 - 4
AppBuilder/AppBuilder/FourthTabViewController.swift

@@ -159,7 +159,7 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
                             Item(icon: UIImage(systemName: "person"), title: "Personal Information".localized()),
                             Item(icon: UIImage(systemName: "textformat.abc"), title: "Change Language".localized()),
                             Item(icon: UIImage(systemName: "textformat.size"), title: "Chat Font Size".localized()),
-                            Item(icon: UIImage(systemName: "photo"), title: "Chat Wallpaper".localized()),
+//                            Item(icon: UIImage(systemName: "photo"), title: "Chat Wallpaper".localized()),
 //                            Item(icon: UIImage(systemName: "person.crop.rectangle"), title: "Change Admin / Internal Password".localized()),
                             Item(icon: UIImage(systemName: "laptopcomputer.and.iphone"), title: "Sign-In to Web".localized()),
 //                            Item(icon: UIImage(named: "ic_internal", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, title: "Set Internal Account".localized()),
@@ -170,7 +170,7 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
                             Item(icon: UIImage(systemName: "person"), title: "Personal Information".localized()),
                             Item(icon: UIImage(systemName: "textformat.abc"), title: "Change Language".localized()),
                             Item(icon: UIImage(systemName: "textformat.size"), title: "Chat Font Size".localized()),
-                            Item(icon: UIImage(systemName: "photo"), title: "Chat Wallpaper".localized()),
+//                            Item(icon: UIImage(systemName: "photo"), title: "Chat Wallpaper".localized()),
                             Item(icon: UIImage(systemName: "laptopcomputer.and.iphone"), title: "Sign-In to Web".localized()),
                         ]
                     } else {
@@ -178,7 +178,7 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
                             Item(icon: UIImage(systemName: "person"), title: "Personal Information".localized()),
                             Item(icon: UIImage(systemName: "textformat.abc"), title: "Change Language".localized()),
                             Item(icon: UIImage(systemName: "textformat.size"), title: "Chat Font Size".localized()),
-                            Item(icon: UIImage(systemName: "photo"), title: "Chat Wallpaper".localized()),
+//                            Item(icon: UIImage(systemName: "photo"), title: "Chat Wallpaper".localized()),
 //                            Item(icon: UIImage(systemName: "person.badge.key"), title: "Access Admin / Internal Features".localized()),
                         ]
                     }
@@ -263,7 +263,7 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
                         Item(icon: UIImage(systemName: "person"), title: "Personal Information".localized()),
                         Item(icon: UIImage(systemName: "textformat.abc"), title: "Change Language".localized()),
                         Item(icon: UIImage(systemName: "textformat.size"), title: "Chat Font Size".localized()),
-                        Item(icon: UIImage(systemName: "photo"), title: "Chat Wallpaper".localized()),
+//                        Item(icon: UIImage(systemName: "photo"), title: "Chat Wallpaper".localized()),
 //                        Item(icon: UIImage(systemName: "person.badge.key"), title: "Access Admin / Internal Features".localized()),
                     ]
                     Item.menus["Personal"]?.append(Item(icon: UIImage(systemName: "arrow.up.and.person.rectangle.portrait"), title: "Sign-Up/Sign-In".localized()))

+ 1 - 1
AppBuilder/AppBuilder/SceneDelegate.swift

@@ -33,7 +33,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
 
                 // ⏳Wait for 5 seconds, then remove.
                 //
-                DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1))
+                DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2))
                 {
                     UIView.animate(withDuration: -1, // system default
                         animations:

+ 21 - 6
AppBuilder/AppBuilder/SecondTabViewController.swift

@@ -36,7 +36,7 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
     
     var noData = false
     var loadingData = true
-    var timerReloadData = Timer()
+    var timerReloadData: Timer?
     
     let textViewSearch = UITextField()
     let buttonImageVoiceSb = UIButton(type: .custom)
@@ -225,6 +225,7 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
         NotificationCenter.default.addObserver(self, selector: #selector(onReload(notification:)), name: NSNotification.Name(rawValue: "onUpdatePersonInfo"), object: nil)
         NotificationCenter.default.addObserver(self, selector: #selector(onReloadTab(notification:)), name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil)
         NotificationCenter.default.addObserver(self, selector: #selector(onReloadTab(notification:)), name: NSNotification.Name(rawValue: "onTopic"), object: nil)
+        NotificationCenter.default.addObserver(self, selector: #selector(onDisconnected(notification:)), name: NSNotification.Name(rawValue: "disconnected_nexilis"), object: nil)
         
         imageViewSearch = UIImageView(image: UIImage(named: self.traitCollection.userInterfaceStyle == .dark ? "nx_search_bar_dark" : "nx_search_bar", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!)
         imageViewSearch.contentMode = .scaleToFill
@@ -767,11 +768,25 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
     }
     
     private func reloadAllData() {
-        timerReloadData.invalidate()
-        timerReloadData = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: {_ in
-            self.getData()
-        })
-        timerReloadData.fire()
+        DispatchQueue.main.async { [weak self] in
+            self?.timerReloadData?.invalidate()
+            self?.timerReloadData = nil
+            self?.timerReloadData = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { [weak self] _ in
+                self?.getData()
+                self?.timerReloadData = nil
+            }
+        }
+    }
+    
+    @objc func onDisconnected(notification: NSNotification) {
+        DispatchQueue.main.async {
+            self.loadingData = true
+            self.chatGroupMaps.removeAll()
+            self.chats.removeAll()
+            self.groups.removeAll()
+            self.tableView.reloadData()
+            self.reloadAllData()
+        }
     }
     
     @objc func onReloadTab(notification: NSNotification) {

+ 8 - 0
AppBuilder/AppBuilder/ThirdTabViewController.swift

@@ -105,6 +105,7 @@ class ThirdTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
         
         NotificationCenter.default.addObserver(self, selector: #selector(onShowAC(notification:)), name: NSNotification.Name(rawValue: "onShowAC"), object: nil)
         NotificationCenter.default.addObserver(self, selector: #selector(onRefreshWebView(notification:)), name: NSNotification.Name(rawValue: "onRefreshWebView"), object: nil)
+        NotificationCenter.default.addObserver(self, selector: #selector(onResumeWebView(notification:)), name: UIApplication.willEnterForegroundNotification, object: nil)
         
         imageVideoPicker = ImageVideoPicker(presentationController: self, delegate: self)
         ThirdTabViewController.canLoadURL = true
@@ -245,6 +246,10 @@ class ThirdTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
         ThirdTabViewController.forceRefresh = true
     }
     
+    @objc func onResumeWebView(notification: NSNotification) {
+        Nexilis.reloadCookies(webView: webView)
+    }
+    
     override func viewWillDisappear(_ animated: Bool) {
         if self.webView != nil {
             if self.webView.scrollView.contentOffset.y < 0 { // Move tableView to top
@@ -285,6 +290,9 @@ class ThirdTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
     }
 
     func hideTabBar() {
+        if UIApplication.shared.windows.first == nil {
+            return
+        }
         var viewController = UIApplication.shared.windows.first!.rootViewController
         if !(viewController is ViewController) {
             viewController = self.parent

+ 1 - 1
NexilisLite/NexilisLite.podspec

@@ -23,7 +23,7 @@ Pod::Spec.new do |spec|
   spec.source_files = 'NexilisLite/Source/**/*'
   spec.resource_bundles = { 'NexilisLite' => ['NexilisLite/Resource/**/*']}
   spec.swift_version = '5.5.1'
-  spec.dependency 'FMDB', '~> 2.7.12'
+  spec.dependency 'FMDB/SQLCipher', '~> 2.7.12'
   spec.dependency 'nuSDKService', '4.0.22'
   spec.dependency 'NotificationBannerSwift'
   spec.dependency 'Alamofire', '~> 5.10.2'

BIN
NexilisLite/NexilisLite/.DS_Store


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

@@ -417,3 +417,5 @@
 "To place chats, tap ⊕ at the top and select a contact." = "Untuk memulai obrolan, ketuk ⊕ di bagian atas dan pilih kontak.";
 "Update Now" = "Perbarui Sekarang";
 "Later" = "Nanti";
+"Restart Required" = "Perlu Memulai Ulang Aplikasi";
+"Oops! Something went wrong. Please restart the app to continue." = "Ups! Terjadi kesalahan. Silakan mulai ulang aplikasi untuk melanjutkan.";

+ 36 - 2
NexilisLite/NexilisLite/Source/APIS.swift

@@ -1592,11 +1592,16 @@ public class APIS: NSObject {
 //        }
         notifTimer.invalidate()
         stopNotif = true
+        if Utils.getSecureFolderOffline() == "0" {
+            Database.shared.database = nil
+            FileEncryption.shared.aesKey = nil
+            FileEncryption.shared.aesIV = nil
+        }
     }
     
     public static var notifTimer = Timer()
     public static var stopNotif = false
-//    public static var afterEnterForeground = false
+    public static var afterEnterBackground = false
     public static func enterForeground() {
         APIS.checkNotificationPermission(completion: { isAllowed in
             if !isAllowed {
@@ -1637,7 +1642,18 @@ public class APIS: NSObject {
                 }
             }
         }
-//        afterEnterForeground = true
+        if Utils.getSecureFolderOffline() == "0" && afterEnterBackground && Database.shared.database == nil && Utils.getSetProfile() {
+            Database.recreateInstance()
+            NotificationCenter.default.post(name: NSNotification.Name(rawValue: "disconnected_nexilis"), object: nil, userInfo: nil)
+            if let navigationC = UIApplication.shared.visibleViewController as? UINavigationController {
+                if navigationC.viewControllers[navigationC.viewControllers.count - 1] is EditorPersonal || navigationC.viewControllers[navigationC.viewControllers.count - 1] is EditorGroup {
+                    navigationC.popViewController(animated: true)
+                }
+            }
+//            Nexilis.getFeatureAccessWithKey(key: ["secure_folder_encrypt_key", "secure_folder_encrypt_iv", "secure_folder_offline"])
+            Nexilis.getFeatureAccess()
+        }
+        afterEnterBackground = true
     }
     
     public static func willEnterForeground() {
@@ -1690,6 +1706,24 @@ public class APIS: NSObject {
         }
     }
     
+    static func showRestartApp() {
+        alertControllerExpired = LibAlertController(
+            title: "Restart Required".localized(),
+            message: "Oops! Something went wrong. Please restart the app to continue.".localized(),
+            preferredStyle: .alert
+        )
+        
+        alertControllerExpired.addAction(UIAlertAction(title: "Ok".localized(), style: .default, handler: { _ in
+            exit(0)
+        }))
+        
+        if UIApplication.shared.visibleViewController?.navigationController != nil {
+            UIApplication.shared.visibleViewController?.navigationController?.present(alertControllerExpired, animated: true, completion: nil)
+        } else {
+            UIApplication.shared.visibleViewController?.present(alertControllerExpired, animated: true, completion: nil)
+        }
+    }
+    
     private static var alertControllerExpired: LibAlertController!
     public static func showExpiredVersion(mandatory: Bool) {
         func showAl() {

+ 22 - 1
NexilisLite/NexilisLite/Source/Callback.swift

@@ -139,6 +139,7 @@ class NetworkMonitor {
     private var isMonitoring = false
     var isConnected: Bool = false
     var fromDisconnect = false
+    var timerReloadData: Timer?
     
     private init() {}
     
@@ -152,21 +153,41 @@ class NetworkMonitor {
                 OutgoingThread.default.set(wait: !connected)
                 if !connected {
                     fromDisconnect = true
-                    DispatchQueue.main.async {
+                    DispatchQueue.main.async { [self] in
                         let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
                         imageView.tintColor = .white
                         let banner = FloatingNotificationBanner(title: "Check your connection".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
                         banner.show()
+                        if Utils.getSecureFolderEncrypt().isEmpty && Database.shared.database != nil {
+                            timerReloadData?.invalidate()
+                            timerReloadData = Timer.scheduledTimer(withTimeInterval: 3, repeats: false) {_ in
+                                Database.shared.database = nil
+                                FileEncryption.shared.aesKey = nil
+                                FileEncryption.shared.aesIV = nil
+                                Database.recreateInstance()
+                                NotificationCenter.default.post(name: NSNotification.Name(rawValue: "disconnected_nexilis"), object: nil, userInfo: nil)
+                                if let navigationC = UIApplication.shared.visibleViewController as? UINavigationController {
+                                    if navigationC.viewControllers[navigationC.viewControllers.count - 1] is EditorPersonal || navigationC.viewControllers[navigationC.viewControllers.count - 1] is EditorGroup {
+                                        navigationC.popViewController(animated: true)
+                                    }
+                                }
+                            }
+                        }
                     }
                 }
                 if connected && fromDisconnect {
                     fromDisconnect = false
                     DispatchQueue.main.async {
+                        self.timerReloadData?.invalidate()
+                        self.timerReloadData = nil
                         let imageView = UIImageView(image: UIImage(systemName: "checkmark.circle.fill"))
                         imageView.tintColor = .white
                         let banner = FloatingNotificationBanner(title: "You're Connected".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .success, colors: nil, iconPosition: .center)
                         banner.show()
                     }
+                    if Database.shared.database == nil {
+                        Nexilis.getFeatureAccess()
+                    }
                 }
             })
         }

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

@@ -2530,6 +2530,20 @@ public class CoreMessage_TMessageBank {
         tmessage.mCode = CoreMessage_TMessageCode.FEATURE_ACCESS_ALL
         tmessage.mStatus = CoreMessage_TMessageUtil.getTID()
         tmessage.mPIN = User.getMyPin() ?? ""
+        tmessage.mBodies[CoreMessage_TMessageKey.API] = Nexilis.sAPIKey
+        tmessage.mBodies[CoreMessage_TMessageKey.TYPE] = "1"
+        tmessage.mBodies[CoreMessage_TMessageKey.ANDROID_APP_NAME] = APIS.getAppNm()
+        return tmessage
+    }
+    
+    public static func getFeatureAccessWithKey(key: [String]) -> TMessage {
+        let tmessage = TMessage()
+        tmessage.mCode = CoreMessage_TMessageCode.FEATURE_ACCESS_KEY
+        tmessage.mStatus = CoreMessage_TMessageUtil.getTID()
+        tmessage.mPIN = User.getMyPin() ?? ""
+        tmessage.mBodies[CoreMessage_TMessageKey.API] = Nexilis.sAPIKey
+        tmessage.mBodies[CoreMessage_TMessageKey.ANDROID_APP_NAME] = APIS.getAppNm()
+        tmessage.mBodies[CoreMessage_TMessageKey.KEY] = "\(key)"
         return tmessage
     }
     

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

@@ -778,6 +778,7 @@ public class CoreMessage_TMessageCode {
     public static let CANCEL_CALL_NOTIFICATION = "CCN";
     
     public static let FEATURE_ACCESS_ALL = "FA02";
+    public static let FEATURE_ACCESS_KEY = "FA03";
     
     public static let PAYMENT_NOTIFICATION = "PAY";
     

+ 57 - 32
NexilisLite/NexilisLite/Source/Database.swift

@@ -15,12 +15,22 @@ public class Database {
     
     public init() {
         let databasePath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/encrypted_db_es.db"
-        if FileManager.default.fileExists(atPath: databasePath) {
-            database = setupDatabaseQueue(withPath: databasePath)
+        if FileManager.default.fileExists(atPath: databasePath) && database == nil {
+            _ = FileEncryption.shared.encryptFileToServer(data: Data())
+            DispatchQueue.global().async {
+                while FileEncryption.shared.aesKey == nil {
+                    Thread.sleep(forTimeInterval: 1)
+                }
+                self.setDBInstance()
+            }
         }
     }
     
-    public static let shared = Database()
+    public static func recreateInstance() {
+        Database.shared = Database()
+    }
+    
+    public static var shared = Database()
     
     public var database: FMDatabaseQueue!
     
@@ -44,42 +54,57 @@ public class Database {
         
         return dbQueue
     }
+    
+    func setDBInstance() {
+        let databasePath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/encrypted_db_es.db"
+        database = setupDatabaseQueue(withPath: databasePath)
+        database?.inDatabase({(fmdb) in
+            let p = Utils.getPassEncDB()
+            if p.isEmpty {
+                let pragmas = [
+                    "PRAGMA cipher_page_size = 1024",
+                    "PRAGMA kdf_iter = 64000",
+                    "PRAGMA cipher_hmac_algorithm = HMAC_SHA512",
+                    "PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA512",
+                    "PRAGMA cipher = 'aes-256-gcm'"
+                ]
+                
+                for pragma in pragmas {
+                    if !fmdb.executeStatements(pragma) {
+                        print("Failed to set pragma: \(pragma)")
+                    }
+                }
+                let key = FileEncryption.shared.aesKey?.withUnsafeBytes { Data($0) }
+                let keyIv = FileEncryption.shared.aesIV
+                var keyString = ""
+                if key != nil {
+                    keyString = key!.base64EncodedString()
+                }
+                if keyIv != nil {
+                    keyString += keyIv!.base64EncodedString()
+                }
+                fmdb.setKey(keyString)
+            }
+            NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
+        })
+    }
 
     
     func openDatabase() -> Int {
-        if let key = Utils.getPasswordDB() {
-            let databasePath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/encrypted_db_es.db"
-            database = setupDatabaseQueue(withPath: databasePath)
-            
-            database?.inDatabase({(fmdb) in
-                fmdb.setKey(key)
-//                print("Open Done")
-            })
-            database?.inTransaction({(fmdb, rollback) in
-                do {
-                    try createDatabase(fmdb: fmdb)
+        setDBInstance()
+        var result = 0
+        database?.inTransaction({(fmdb, rollback) in
+            do {
+                try createDatabase(fmdb: fmdb)
+                result = 1
 //                    print("Create Done")
-                } catch {
-                }
-            })
-            return 1
-        }
-        return 0
+            } catch {
+            }
+        })
+        return result
     }
     
     func createDatabase(fmdb:FMDatabase) throws -> Void{
-        let pragmas = [
-            "PRAGMA cipher_page_size = 1024;",
-            "PRAGMA kdf_iter = 64000;",
-            "PRAGMA cipher_hmac_algorithm = 'HMAC_SHA512';",
-            "PRAGMA cipher_kdf_algorithm = 'PBKDF2_HMAC_SHA512';",
-            "PRAGMA cipher = 'aes-256-gcm';"
-        ]
-        
-        for pragma in pragmas {
-            fmdb.executeUpdate(pragma, withArgumentsIn: [])
-        }
-        
         try fmdb.executeUpdate("CREATE TABLE IF NOT EXISTS 'BUDDY' (" +
                                 "'_id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
                                 "'f_pin' text NOT NULL UNIQUE," +

+ 4 - 3
NexilisLite/NexilisLite/Source/Download.swift

@@ -24,6 +24,7 @@ public class Download {
     var DOWNLOAD_BUFFER = [Data?]()
     var DOWNLOAD_SESSION = [Session]()
     var DOWNLOAD_URL = Utils.getURLBase() + "filepalio/image/"
+    var DOWNLOAD_URL_BACKUP = Utils.getURLBase() + "filepalio/backuprestore/"
     
     public func start(forKey: String, delegate: DownloadDelegate){
         self.delegate = delegate
@@ -49,8 +50,8 @@ public class Download {
         startHTTP(filename: forKey, baseURL: downloadUrl, completion: completion)
     }
     
-    public func startHTTP(forKey: String, completion: @escaping (String, Double)->()) {
-        startHTTP(filename: forKey, baseURL: DOWNLOAD_URL, completion: completion)
+    public func startHTTP(forKey: String, isBackup: Bool = false, completion: @escaping (String, Double)->()) {
+        startHTTP(filename: forKey, baseURL: isBackup ? DOWNLOAD_URL_BACKUP : DOWNLOAD_URL, completion: completion)
     }
     
     public func startHTTP(filename: String, baseURL: String, completion: @escaping (String, Double)->()) {
@@ -87,7 +88,7 @@ public class Download {
                 }
                 .responseData { result in
                     if let response = result.response, response.statusCode == 200, let successResponse = result.value {
-                        print("Response success")
+//                        print("Response success")
                         do {
                             let dResponse = FileEncryption.shared.decryptFileFromServer(data: successResponse)
                             if dResponse != nil {

+ 14 - 24
NexilisLite/NexilisLite/Source/Extension.swift

@@ -1281,8 +1281,6 @@ public class ImageCache {
     }
     
     func loadCacheFromDisk(directory: URL) {
-        let fileManager = FileManager.default
-
         // Load mapping
         let mappingFilePath = directory.appendingPathComponent("keyMapping.json")
         guard let jsonData = try? Data(contentsOf: mappingFilePath),
@@ -1295,32 +1293,24 @@ public class ImageCache {
         }
 
         // Load images
-        do {
-            let fileURLs = try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil)
-            for fileURL in fileURLs {
-                if fileURL.lastPathComponent == "keyMapping.json" { continue } // Skip mapping file
-
-                let sanitizedKey = fileURL.deletingPathExtension().lastPathComponent
-                let imageName = "\(sanitizedKey).png"
-                if FileEncryption.shared.isSecureExists(filename: imageName) {
-                    do {
-                        if var data = try FileEncryption.shared.readSecure(filename: imageName) {
-                            let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
-                            if dataDecrypt != nil {
-                                data = dataDecrypt!
-                            }
-                            if let image = UIImage(data: data) {
-                                cache.setObject(image, forKey: sanitizedKey as NSString)
-                            }
+        for cacheKey in cacheKeyMap {
+            let imageName = "\(cacheKey.value).png"
+            if FileEncryption.shared.isSecureExists(filename: imageName) {
+                do {
+                    if var data = try FileEncryption.shared.readSecure(filename: imageName) {
+                        let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
+                        if dataDecrypt != nil {
+                            data = dataDecrypt!
+                        }
+                        if let image = UIImage(data: data) {
+                            cache.setObject(image, forKey: cacheKey.value as NSString)
                         }
-                    }
-                    catch {
-                        print("Error reading secure file")
                     }
                 }
+                catch {
+                    print("Error reading secure file")
+                }
             }
-        } catch {
-//            print("Failed to load cache from disk: \(error)")
         }
     }
     

+ 31 - 19
NexilisLite/NexilisLite/Source/FileEncryption.swift

@@ -19,6 +19,9 @@ public class FileEncryption {
     private init() {}
     
     public func readSecure(filename: String) throws -> Data? {
+        if Utils.getFeatureAccess().isEmpty {
+            return nil
+        }
         let fileManager = FileManager.default
         let documentDir = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
         let fileURL = documentDir.appendingPathComponent("Secure").appendingPathComponent(filename)
@@ -31,26 +34,35 @@ public class FileEncryption {
     }
     
     public func writeSecure(filename: String? = nil, data: Data? = nil) throws {
-        let fileManager = FileManager.default
+        DispatchQueue.global().async {
+            do {
+                while Utils.getFeatureAccess().isEmpty {
+                    Thread.sleep(forTimeInterval: 1)
+                }
+                let fileManager = FileManager.default
 
-        // Get the app's Documents directory
-        let documentDir = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
+                // Get the app's Documents directory
+                let documentDir = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
 
-        // Optional: Create a "Secure" subdirectory
-        let secureDir = documentDir.appendingPathComponent("Secure", isDirectory: true)
-        if !fileManager.fileExists(atPath: secureDir.path) {
-            try fileManager.createDirectory(at: secureDir, withIntermediateDirectories: true)
-        }
+                // Optional: Create a "Secure" subdirectory
+                let secureDir = documentDir.appendingPathComponent("Secure", isDirectory: true)
+                if !fileManager.fileExists(atPath: secureDir.path) {
+                    try fileManager.createDirectory(at: secureDir, withIntermediateDirectories: true)
+                }
 
-        // Create the file URL
-        let fileURL = secureDir.appendingPathComponent(filename ?? "")
+                // Create the file URL
+                let fileURL = secureDir.appendingPathComponent(filename ?? "")
 
-        // Encrypt the data
-        let sealedBox = try AES.GCM.seal(data ?? Data(), using: MasterKeyUtil.shared.getMasterKey())
-        let encryptedData = sealedBox.combined!
+                // Encrypt the data
+                let sealedBox = try AES.GCM.seal(data ?? Data(), using: MasterKeyUtil.shared.getMasterKey())
+                let encryptedData = sealedBox.combined!
 
-        // Write encrypted data to file
-        try encryptedData.write(to: fileURL)
+                // Write encrypted data to file
+                try encryptedData.write(to: fileURL)
+            } catch {
+                
+            }
+        }
     }
     
     public func isSecureExists(filename: String) -> Bool {
@@ -66,10 +78,10 @@ public class FileEncryption {
     
     public func decryptFileFromServer(data: Data) -> Data? {
         do {
-            if aesKey == nil {
+            if aesKey == nil && !Utils.getSecureFolderEncrypt().isEmpty {
                 aesKey = try getAESKey()
             }
-            if aesIV == nil {
+            if aesIV == nil && !Utils.getSecureFolderEncryptIv().isEmpty {
                 aesIV = try getAESIV()
             }
         } catch {
@@ -95,10 +107,10 @@ public class FileEncryption {
     
     func encryptFileToServer(data: Data) -> Data? {
         do {
-            if aesKey == nil {
+            if aesKey == nil && !Utils.getSecureFolderEncrypt().isEmpty {
                 aesKey = try getAESKey()
             }
-            if aesIV == nil {
+            if aesIV == nil && !Utils.getSecureFolderEncryptIv().isEmpty {
                 aesIV = try getAESIV()
             }
         } catch {

+ 13 - 4
NexilisLite/NexilisLite/Source/Network.swift

@@ -19,6 +19,7 @@ public class Network {
     private var progress = 0.0
     private var CHUNK_SIZE = 200 * 1024
     private var UPLOAD_URL = Utils.getURLBase() + "uploader"
+    private var UPLOAD_URL_BACKUP = Utils.getURLBase() + "uploader_bkp"
     
     public init() {}
     
@@ -190,7 +191,7 @@ public class Network {
     }
     
     public func uploadHTTP(fileUrl: URL, completion: @escaping (Bool, Double)->()) {
-        _ = uploadHTTP(UPLOAD_URL, files: [fileUrl], completion: completion)
+        _ = uploadHTTP(UPLOAD_URL_BACKUP, files: [fileUrl], completion: completion)
     }
     
     public func uploadHTTP(_ endUrl: String, files: [URL] = [], filename: [String] = [], parameters: [String : Any] = [:], completion: @escaping (Bool, Double)->()) -> UploadRequest {
@@ -243,9 +244,17 @@ public class Network {
             }
             
             for i in 0..<filesIn.count {
-                let mime = self.mimeType(for: filesIn[i])
-                multipartFormData.append(filesIn[i], withName: "file\(i+1)", fileName: filesIn[i].lastPathComponent, mimeType: mime)
-                Nexilis.putUploadFile(forKey: filesIn[i].lastPathComponent, uploader: self)
+                let urlFile = filesIn[i]
+                do {
+                    var dataToUpload = try Data(contentsOf: urlFile)
+                    if Nexilis.checkingAccess(key: "secure_folder_encrypt"){
+                        dataToUpload = FileEncryption.shared.encryptFileToServer(data: dataToUpload) ?? Data()
+                    }
+                    let mime = self.mimeType(for: filesIn[i])
+                    multipartFormData.append(dataToUpload, withName: "file\(i+1)", fileName: filesIn[i].lastPathComponent, mimeType: mime)
+                    Nexilis.putUploadFile(forKey: filesIn[i].lastPathComponent, uploader: self)
+                } catch {
+                }
                 //print(multipartFormData)
             }
             

+ 107 - 49
NexilisLite/NexilisLite/Source/Nexilis.swift

@@ -16,9 +16,10 @@ import QuickLook
 import NotificationBannerSwift
 import SDWebImage
 import CryptoKit
+import WebKit
 
 public class Nexilis: NSObject {
-    public static var cpaasVersion = "5.0.35"
+    public static var cpaasVersion = "5.0.39"
     public static var sAPIKey = ""
     
     public static var ADDRESS = ""
@@ -166,13 +167,42 @@ public class Nexilis: NSObject {
         imageCache.countLimit = 100
         imageCache.totalCostLimit = 1024 * 1024 * 200
         
-        UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum)
-        
         DispatchQueue.global().async {
             do {
+                func forceShowFB() {
+                    DispatchQueue.main.async {
+                        var viewController = UIApplication.shared.windows.first?.rootViewController
+                        var notNull = false
+                        while !notNull {
+                            viewController = UIApplication.shared.windows.first?.rootViewController
+                            if viewController != nil && Utils.getFinishInitPrefsr() {
+                                notNull = true
+                            }
+                        }
+                        if showButton {
+                            addFB(viewController: viewController!, fromMAB: fromMAB)
+                        }
+                        if let rootViewController = viewController {
+                            let isDarkMode = rootViewController.traitCollection.userInterfaceStyle == .dark
+                            if isDarkMode {
+                                UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).defaultTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
+                                let cancelButtonAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
+                                UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes , for: .normal)
+                            } else {
+                                UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).defaultTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.black]
+                                let cancelButtonAttributes = [NSAttributedString.Key.foregroundColor: UIColor.black, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
+                                UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes , for: .normal)
+                            }
+                        }
+                    }
+                }
                 if Utils.getFinishInitPrefsr() {
                     Utils.setFinishInitPrefs(value: false)
                 }
+                if !Utils.getPrefTheme().isEmpty {
+                    forceShowFB()
+                    Utils.setFinishInitPrefs(value: true)
+                }
                 let address = Nexilis.getAddressNew(apiKey:apiKey)
                 if address.isEmpty {
                     return
@@ -212,7 +242,6 @@ public class Nexilis: NSObject {
                 
                 if let me = User.getMyPin() {
                     if Utils.getSetProfile() {
-                        _ = Database.shared.openDatabase()
                         Database.shared.database?.inTransaction({ (fmdb, rollback) in
                             do {
                                 if let cursorData = Database.shared.getRecords(fmdb: fmdb, query: "SELECT * FROM BUDDY where f_pin = '\(me)' ") {
@@ -245,11 +274,6 @@ public class Nexilis: NSObject {
                         })
                     } else if isShowForceSignIn && !Utils.getForceAnonymous() && !Utils.getSetProfile() {
                         DispatchQueue.main.async {
-                            do {
-                                try _ = MasterKeyUtil.shared.getMasterKey()
-                            } catch {
-                                
-                            }
                             showForceSignIn()
                         }
                     }
@@ -257,31 +281,7 @@ public class Nexilis: NSObject {
                     getPullWorkingArea()
                     getPullGroupNoMember()
                     delegate.onSuccess(userId: me)
-                    DispatchQueue.main.async {
-                        var viewController = UIApplication.shared.windows.first?.rootViewController
-                        var notNull = false
-                        while !notNull {
-                            viewController = UIApplication.shared.windows.first?.rootViewController
-                            if viewController != nil && Utils.getFinishInitPrefsr() {
-                                notNull = true
-                            }
-                        }
-                        if showButton {
-                            addFB(viewController: viewController!, fromMAB: fromMAB)
-                        }
-                        if let rootViewController = viewController {
-                            let isDarkMode = rootViewController.traitCollection.userInterfaceStyle == .dark
-                            if isDarkMode {
-                                UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).defaultTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
-                                let cancelButtonAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
-                                UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes , for: .normal)
-                            } else {
-                                UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).defaultTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.black]
-                                let cancelButtonAttributes = [NSAttributedString.Key.foregroundColor: UIColor.black, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
-                                UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes , for: .normal)
-                            }
-                        }
-                    }
+                    forceShowFB()
                     if (Utils.getSetProfile() && !Utils.getFinishInitPrefsr()) || (!Utils.getForceAnonymous() && !Utils.getFinishInitPrefsr()) {
                         Utils.setFinishInitPrefs(value: true)
                     }
@@ -499,17 +499,36 @@ public class Nexilis: NSObject {
         }
     }
     
-    private static func getFeatureAccess() {
+    static func getFeatureAccessWithKey(key: [String]) {
         DispatchQueue.global().async {
-            Utils.postDataWithCookiesAndUserAgent(from: URL(string: Utils.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) {
-                    if let jsonArray = try? JSONSerialization.jsonObject(with: responseString.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [AnyObject] {
-                        do {
+            if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getFeatureAccessWithKey(key: key), timeout: 5000) {
+                print("RESPOND: \(response.toLogString())")
+            } else {
+                print("ERROR")
+            }
+        }
+    }
+    
+    static func getFeatureAccess() {
+        DispatchQueue.global().async {
+//            Utils.postDataWithCookiesAndUserAgent(from: URL(string: Utils.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) {
+//                    if let jsonArray = try? JSONSerialization.jsonObject(with: responseString.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [AnyObject] {
+//                    }
+//                }
+//            }
+            if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.getFeatureAccessAll(), timeout: 5000), response.isOk() {
+                let data = response.getBody(key: CoreMessage_TMessageKey.DATA, default_value: "[]")
+                do {
+                    if let data = data.data(using: .utf8) {
+                        if let jsonArray = try JSONSerialization.jsonObject(with: data, options: []) as? [AnyObject] {
                             var jsonFA: [String: Any] = [:]
+                            var keyTemp = ""
+                            var keyIvTemp = ""
                             for jsonData in jsonArray {
                                 var tmp = jsonData as! [String: Any]
                                 tmp.removeValue(forKey: "action")
@@ -537,15 +556,36 @@ public class Nexilis: NSObject {
                                     Utils.setAuthenticationDuration(value: ad)
                                 }
                                 if jsonData["secure_folder_encrypt_key"]! != nil {
-                                    if let dataKey = Data(base64Encoded: jsonData["secure_folder_encrypt_key"] as! String, options: .ignoreUnknownCharacters) {
+                                    keyTemp = jsonData["secure_folder_encrypt_key"] as! String
+                                    if let dataKey = Data(base64Encoded: keyTemp, options: .ignoreUnknownCharacters) {
                                         FileEncryption.shared.aesKey = SymmetricKey(data: dataKey)
-                                        Utils.setSecureFolderEncrypt(value: jsonData["secure_folder_encrypt_key"] as! String)
+                                        if Utils.getSecureFolderOffline() == "0" {
+                                            Utils.setSecureFolderEncrypt(value: "")
+                                        } else {
+                                            Utils.setSecureFolderEncrypt(value: keyTemp)
+                                        }
                                     }
                                 }
                                 if jsonData["secure_folder_encrypt_key_iv"]! != nil {
-                                    if let dataKey = Data(base64Encoded: jsonData["secure_folder_encrypt_key_iv"] as! String, options: .ignoreUnknownCharacters) {
+                                    keyIvTemp = jsonData["secure_folder_encrypt_key_iv"] as! String
+                                    if let dataKey = Data(base64Encoded: keyIvTemp, options: .ignoreUnknownCharacters) {
                                         FileEncryption.shared.aesIV = dataKey
-                                        Utils.setSecureFolderEncryptIv(value: jsonData["secure_folder_encrypt_key_iv"] as! String)
+                                        if Utils.getSecureFolderOffline() == "0" {
+                                            Utils.setSecureFolderEncryptIv(value: "")
+                                        } else {
+                                            Utils.setSecureFolderEncryptIv(value: keyIvTemp)
+                                        }
+                                    }
+                                }
+                                if jsonData["secure_folder_offline"]! != nil {
+                                    let off = jsonData["secure_folder_offline"] as! String
+                                    Utils.setSecureFolderOffline(value: off)
+                                    if off == "0" {
+                                        Utils.setSecureFolderEncrypt(value: "")
+                                        Utils.setSecureFolderEncryptIv(value: "")
+                                    } else {
+                                        Utils.setSecureFolderEncrypt(value: keyTemp)
+                                        Utils.setSecureFolderEncryptIv(value: keyIvTemp)
                                     }
                                 }
                                 if jsonData["chatbot_greetings"]! != nil {
@@ -555,14 +595,23 @@ public class Nexilis: NSObject {
                                     }
                                 }
                             }
+                            keyTemp = ""
+                            keyIvTemp = ""
                             if let convertJsonFA = try? JSONSerialization.data(withJSONObject: jsonFA, options: .prettyPrinted) {
                                 if let jsonFAString = String(data: convertJsonFA, encoding: .utf8) {
                                     Utils.setFeatureAccess(value: jsonFAString)
                                 }
                             }
-                        } catch {
                         }
                     }
+                } catch {
+                    print("gagal parsing data:")
+                }
+            } else {
+                if Utils.getSecureFolderOffline() == "0" || (Utils.getSecureFolderOffline() == "1" && !Utils.getSetProfile()) {
+                    DispatchQueue.main.async {
+                        APIS.showRestartApp()
+                    }
                 }
             }
         }
@@ -913,6 +962,15 @@ public class Nexilis: NSObject {
         return result
     }
     
+    public static func reloadCookies(webView: WKWebView) {
+        if !Utils.getCookiesMobileForStorage().isEmpty {
+            let cookies = convertJSONStringToCookies(jsonString: Utils.getCookiesMobileForStorage())
+            for cookie in cookies {
+                webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)
+            }
+        }
+    }
+    
     private static func convertCookiesToJSONString(cookies: [HTTPCookie]) -> String? {
         let cookiesArray = cookies.map { cookie -> [String: Any] in
             return [
@@ -1024,7 +1082,7 @@ public class Nexilis: NSObject {
     
     public static func write(message: TMessage, timeout: Int = 15 * 1000) -> String? {
         do {
-            if API.nGetCLXConnState() == 0 {
+            if !API.bInetConnAvailable() {
                 return nil
             }
             //print(">> SENDING MESSAGE >> ", message.toLogString())

+ 13 - 2
NexilisLite/NexilisLite/Source/Utils.swift

@@ -655,7 +655,7 @@ public final class Utils {
         if let value: String = SecureUserDefaults.shared.value(forKey: "secure_folder_encrypt_key") {
             return value
         }
-        return "easySoftIndonesia"
+        return ""
     }
     
     public static func setSecureFolderEncryptIv(value: String){
@@ -666,7 +666,18 @@ public final class Utils {
         if let value: String = SecureUserDefaults.shared.value(forKey: "secure_folder_encrypt_key_iv") {
             return value
         }
-        return "easySoftIndonesia"
+        return ""
+    }
+    
+    public static func setSecureFolderOffline(value: String){
+        SecureUserDefaults.shared.set(value, forKey: "secure_folder_offline")
+    }
+    
+    public static func getSecureFolderOffline() -> String {
+        if let value: String = SecureUserDefaults.shared.value(forKey: "secure_folder_offline") {
+            return value
+        }
+        return "1"
     }
     
     public static func fetchDataWithCookiesAndUserAgent(from url: URL, completion: @escaping (Data?, URLResponse?, Error?) -> ()) {

+ 1 - 1
NexilisLite/NexilisLite/Source/View/Call/QmeraAudioViewController.swift

@@ -782,7 +782,7 @@ class QmeraAudioViewController: UIViewController {
         timerSpeaker?.invalidate()
         tempSpeaker = !tempSpeaker
         speaker.isSelected = tempSpeaker
-        timerSpeaker = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: {_ in
+        timerSpeaker = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: {_ in
             if QmeraAudioViewController.bSpeakerPhone != self.tempSpeaker {
                 QmeraAudioViewController.bSpeakerPhone = !QmeraAudioViewController.bSpeakerPhone
                 self.tempSpeaker = QmeraAudioViewController.bSpeakerPhone

+ 2 - 2
NexilisLite/NexilisLite/Source/View/Call/QmeraVideoViewController.swift

@@ -433,7 +433,7 @@ class QmeraVideoViewController: UIViewController {
             myImage.contentMode = .scaleAspectFill
         }
 //        let idMe = User.getMyPin() as String?
-//        Database().database?.inTransaction({ fmdb, rollback in
+//        Database.shared.database?.inTransaction({ fmdb, rollback in
 //            if let c = Database().getRecords(fmdb: fmdb, query: "select image_id from BUDDY where f_pin = '\(idMe!)'"), c.next() {
 //                let image = c.string(forColumnIndex: 0)!
 //                if image.isEmpty {
@@ -1150,7 +1150,7 @@ class QmeraVideoViewController: UIViewController {
                 self.buttonSpeaker.tintColor = .mainColor
                 self.buttonSpeaker.setImage(UIImage(systemName: "speaker.slash", withConfiguration: UIImage.SymbolConfiguration(pointSize: 30, weight: .medium, scale: .default)), for: .normal)
             }
-            self.timerSpeaker = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: {_ in
+            self.timerSpeaker = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: {_ in
                 if QmeraVideoViewController.bSpeakerPhone != self.tempSpeaker {
                     QmeraVideoViewController.bSpeakerPhone = !QmeraVideoViewController.bSpeakerPhone
                     self.tempSpeaker = QmeraVideoViewController.bSpeakerPhone

+ 1 - 1
NexilisLite/NexilisLite/Source/View/Call/VideoConferenceViewController.swift

@@ -359,7 +359,7 @@ class VideoConferenceViewController: UIViewController {
             myImage.contentMode = .scaleAspectFill
         }
 //        let idMe = User.getMyPin() as String?
-//        Database().database?.inTransaction({ fmdb, rollback in
+//        Database.shared.database?.inTransaction({ fmdb, rollback in
 //            if let c = Database().getRecords(fmdb: fmdb, query: "select image_id from BUDDY where f_pin = '\(idMe!)'"), c.next() {
 //                let image = c.string(forColumnIndex: 0)!
 //                if image.isEmpty {

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

@@ -1530,7 +1530,7 @@ extension ChatGPTBotView: UIContextMenuInteractionDelegate {
     
     private func getDataProfile(message_id: String) -> [String: String]{
         var data: [String: String] = [:]
-        Database().database?.inTransaction({ fmdb, rollback in
+        Database.shared.database?.inTransaction({ fmdb, rollback in
             if let c = Database().getRecords(fmdb: fmdb, query: "select f_display_name from MESSAGE where message_id = '\(message_id)'"), c.next() {
                 data["name"] = c.string(forColumnIndex: 0)!
                 c.close()

+ 35 - 17
NexilisLite/NexilisLite/Source/View/Chat/EditorGroup.swift

@@ -584,7 +584,7 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
     
     func getDataProfile(f_pin: String, message_id: String) -> [String: String]{
         var data: [String: String] = [:]
-        Database().database?.inTransaction({ fmdb, rollback in
+        Database.shared.database?.inTransaction({ fmdb, rollback in
             if let c = Database().getRecords(fmdb: fmdb, query: "select first_name || ' ' || last_name, image_id from BUDDY where f_pin = '\(f_pin)'"), c.next() {
                 data["name"] = c.string(forColumnIndex: 0)!.trimmingCharacters(in: .whitespacesAndNewlines)
                 data["image_id"] = c.string(forColumnIndex: 1)!
@@ -1426,7 +1426,7 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
         let data:[AnyHashable : Any] = notification.userInfo!
         if data["code"]  as? String ?? "" == "A010" && data["groupId"]  as? String ?? "" == self.dataGroup["group_id"]  as? String ?? "" {
             DispatchQueue.main.async {
-                Database().database?.inTransaction({ fmdb, rollback in
+                Database.shared.database?.inTransaction({ fmdb, rollback in
                     if let c = Database().getRecords(fmdb: fmdb, query: "select f_name, image_id from GROUPZ where group_id = '\(self.dataGroup["group_id"]!!)'"), c.next() {
                         self.dataGroup["f_name"] = c.string(forColumnIndex: 0)!.trimmingCharacters(in: .whitespacesAndNewlines)
                         self.dataGroup["image_id"] = c.string(forColumnIndex: 1)!
@@ -1973,7 +1973,7 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
     }
     
     private func getCounter() {
-        Database().database?.inTransaction({ fmdb, rollback in
+        Database.shared.database?.inTransaction({ fmdb, rollback in
             var l_pin = self.dataGroup["group_id"] as? String ?? ""
             if (self.dataTopic["chat_id"]  as? String ?? "" != "") {
                 l_pin = self.dataTopic["chat_id"]  as? String ?? ""
@@ -4026,7 +4026,7 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
     
     private func queryMessageReply(message_id: String) -> [String: Any?] {
         var dataQuery: [String: Any] = [:]
-        Database().database?.inTransaction({ fmdb, rollback in
+        Database.shared.database?.inTransaction({ fmdb, rollback in
             if let c = Database().getRecords(fmdb: fmdb, query: "SELECT message_id, f_pin, message_text, attachment_flag, thumb_id, image_id, video_id, file_id FROM MESSAGE where message_id='\(message_id)'"), c.next() {
                 dataQuery["message_id"] = c.string(forColumnIndex: 0)
                 dataQuery["f_pin"] = c.string(forColumnIndex: 1)
@@ -4232,7 +4232,7 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
             
             self.reffId = nil
             UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
-                self.constraintTopTextField.constant = self.constraintTopTextField.constant - 50
+                self.constraintTopTextField.constant = self.constraintTopTextField.constant - 50 - (self.offset()*3)
                 if self.contraintBottomMention.constant > 0 {
                     self.contraintBottomMention.constant = self.contraintBottomMention.constant - 50
                 }
@@ -4899,7 +4899,6 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
 //            }
 //            editedText.bottomAnchor.constraint(equalTo: containerMessage.bottomAnchor).isActive = true
 //        }
-        topMarginText.isActive = true
         if dataMessages[indexPath.row]["attachment_flag"]  as? String ?? "" == "27" || dataMessages[indexPath.row]["attachment_flag"]  as? String ?? "" == "26" {
             messageText.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 85).isActive = true
             let imageLS = UIImageView()
@@ -4950,7 +4949,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
         if let attachmentFlag = dataMessages[indexPath.row]["attachment_flag"], let attachmentFlag = attachmentFlag as? String {
             if attachmentFlag == "27" || attachmentFlag == "26" { // live streaming
                 if let json = try! JSONSerialization.jsonObject(with: textChat.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
-                    Database().database?.inTransaction({ fmdb, rollback in
+                    Database.shared.database?.inTransaction({ fmdb, rollback in
                         let title = json["title"]  as? String ?? ""
                         let description = json["description"]  as? String ?? ""
                         let start = json["time"] as! Int64
@@ -5171,7 +5170,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
             }
         }
         
-        if (thumbChat != "" && dataMessages[indexPath.row]["lock"]  as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
+        if (!thumbChat.isEmpty && dataMessages[indexPath.row]["lock"]  as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
             if let listImages = groupImages[messageIdChat] {
                 timeMessage.isHidden = true
                 statusMessage.isHidden = true
@@ -5523,7 +5522,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
             }
         }
         
-        if (fileChat != "" && dataMessages[indexPath.row]["lock"]  as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
+        if (!fileChat.isEmpty && dataMessages[indexPath.row]["lock"]  as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
             topMarginText.constant = topMarginText.constant + 55
             
             let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
@@ -5826,10 +5825,9 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
             }
         }
         
-        if (reffChat != "") {
+        if (!reffChat.isEmpty) {
             let data = queryMessageReply(message_id: reffChat)
             if data.count != 0 {
-                topMarginText.constant = topMarginText.constant + 55
                 
                 let containerReply = UIView()
                 containerMessage.addSubview(containerReply)
@@ -5848,7 +5846,9 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                     containerReply.bottomAnchor.constraint(equalTo: messageText.topAnchor, constant: -5).isActive = true
                 }
                 containerReply.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
-                containerReply.heightAnchor.constraint(equalToConstant: 50).isActive = true
+                let minHeightConstraint = containerReply.heightAnchor.constraint(greaterThanOrEqualToConstant: 50 + (self.offset()*3))
+                minHeightConstraint.priority = .defaultHigh
+                minHeightConstraint.isActive = true
                 containerReply.backgroundColor = .black.withAlphaComponent(0.2)
                 containerReply.layer.cornerRadius = 5
                 containerReply.clipsToBounds = true
@@ -5897,10 +5897,14 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                 }
                 
                 let contentReply = UILabel()
+                contentReply.numberOfLines = 2
                 containerReply.addSubview(contentReply)
                 contentReply.translatesAutoresizingMaskIntoConstraints = false
                 contentReply.leadingAnchor.constraint(equalTo: leftReply.leadingAnchor, constant: 10).isActive = true
                 contentReply.bottomAnchor.constraint(equalTo: containerReply.bottomAnchor, constant: -10).isActive = true
+                let topConstraintContent = contentReply.topAnchor.constraint(equalTo: titleReply.bottomAnchor)
+                topConstraintContent.priority = .defaultHigh
+                topConstraintContent.isActive = true
                 contentReply.font = UIFont.systemFont(ofSize: 10 + offset())
                 let message_text = data["message_text"] as? String ?? ""
                 let attachment_flag = data["attachment_flag"] as? String  ?? ""
@@ -5912,13 +5916,13 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                     contentReply.trailingAnchor.constraint(equalTo: containerReply.trailingAnchor, constant: -20).isActive = true
                     contentReply.attributedText = message_text.richText(group_id: self.dataGroup["group_id"]  as? String ?? "")
                 } else if (attachment_flag == "1" || image_chat != "") {
-                    if (message_text == "") {
+                    if (message_text.trimmingCharacters(in: .whitespacesAndNewlines) == "") {
                         contentReply.text = "📷 Photo".localized()
                     } else {
                         contentReply.attributedText = message_text.richText(group_id: self.dataGroup["group_id"]  as? String ?? "")
                     }
                 } else if (attachment_flag == "2" || video_chat != "") {
-                    if (message_text == "") {
+                    if (message_text.trimmingCharacters(in: .whitespacesAndNewlines) == "") {
                         contentReply.text = "📹 Video".localized()
                     } else {
                         contentReply.attributedText = message_text.richText(group_id: self.dataGroup["group_id"]  as? String ?? "")
@@ -6043,6 +6047,19 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
             titleForwarded.attributedText = " $\(textForwarded)$".richText()
         }
         
+        if messageText.isDescendant(of: containerMessage) {
+            var addTopMargin = true
+            if !reffChat.isEmpty && dataMessages[indexPath.row]["message_scope_id"]  as? String ?? "" != MessageScope.FORM {
+                let data = queryMessageReply(message_id: reffChat)
+                if data.count != 0 {
+                    addTopMargin = false
+                }
+            }
+            if addTopMargin{
+                topMarginText.isActive = true
+            }
+        }
+        
         return cellMessage
     }
     
@@ -6677,7 +6694,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
             return
         }
         UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
-            self.constraintTopTextField.constant = self.constraintTopTextField.constant + 50
+            self.constraintTopTextField.constant = self.constraintTopTextField.constant + 50 + (self.offset()*3)
             if self.contraintBottomMention.constant > 0 {
                 self.contraintBottomMention.constant = self.contraintBottomMention.constant + self.heightTextFieldSend.constant
             }
@@ -6736,6 +6753,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
         contentReply.translatesAutoresizingMaskIntoConstraints = false
         contentReply.leadingAnchor.constraint(equalTo: leftReply.leadingAnchor, constant: 10).isActive = true
         contentReply.topAnchor.constraint(equalTo: titleReply.bottomAnchor).isActive = true
+        contentReply.trailingAnchor.constraint(equalTo: containerPreviewReply.trailingAnchor, constant: -20).isActive = true
         contentReply.font = UIFont.systemFont(ofSize: 10 + offset())
         let message_text = dataMessages[indexPath.row]["message_text"]  as? String ?? ""
         let attachment_flag = dataMessages[indexPath.row]["attachment_flag"]  as? String ?? ""
@@ -6746,13 +6764,13 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
         if (attachment_flag == "0" && thumb_chat == "") {
             contentReply.attributedText = message_text.richText(group_id: self.dataGroup["group_id"]  as? String ?? "")
         } else if (attachment_flag == "1" || image_chat != "") {
-            if (message_text == "") {
+            if (message_text.trimmingCharacters(in: .whitespacesAndNewlines) == "") {
                 contentReply.text = "📷 Photo".localized()
             } else {
                 contentReply.attributedText = message_text.richText(group_id: self.dataGroup["group_id"]  as? String ?? "")
             }
         } else if (attachment_flag == "2" || video_chat != "") {
-            if (message_text == "") {
+            if (message_text.trimmingCharacters(in: .whitespacesAndNewlines) == "") {
                 contentReply.text = "📹 Video".localized()
             } else {
                 contentReply.attributedText = message_text.richText(group_id: self.dataGroup["group_id"]  as? String ?? "")

+ 33 - 19
NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift

@@ -3479,7 +3479,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
     }
     
     private func getCounter() {
-        Database().database?.inTransaction({ fmdb, rollback in
+        Database.shared.database?.inTransaction({ fmdb, rollback in
             if let c = Database().getRecords(fmdb: fmdb, query: "SELECT counter FROM MESSAGE_SUMMARY where l_pin='\(dataPerson["f_pin"]!!)'"), c.next() {
                 counter = Int(c.int(forColumnIndex: 0))
                 c.close()
@@ -5144,7 +5144,7 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
     
     private func getDataProfile(message_id: String) -> [String: String]{
         var data: [String: String] = [:]
-        Database().database?.inTransaction({ fmdb, rollback in
+        Database.shared.database?.inTransaction({ fmdb, rollback in
             if let c = Database().getRecords(fmdb: fmdb, query: "select f_display_name from MESSAGE where message_id = '\(message_id)'"), c.next() {
                 data["name"] = c.string(forColumnIndex: 0)!
                 c.close()
@@ -5163,7 +5163,7 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
     
     private func queryMessageReply(message_id: String) -> [String: Any?] {
         var dataQuery: [String: Any] = [:]
-        Database().database?.inTransaction({ fmdb, rollback in
+        Database.shared.database?.inTransaction({ fmdb, rollback in
             if let c = Database().getRecords(fmdb: fmdb, query: "SELECT message_id, f_pin, message_text, attachment_flag, thumb_id, image_id, video_id, file_id FROM MESSAGE where message_id='\(message_id)'"), c.next() {
                 dataQuery["message_id"] = c.string(forColumnIndex: 0) ?? ""
                 dataQuery["f_pin"] = c.string(forColumnIndex: 1) ?? ""
@@ -5390,7 +5390,7 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
             
             self.reffId = nil
             UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
-                self.constraintTopTextField.constant = self.constraintTopTextField.constant - 50
+                self.constraintTopTextField.constant = self.constraintTopTextField.constant - 50 - (self.offset()*3)
             }, completion: nil)
         }
     }
@@ -5588,7 +5588,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                 conferenceNav.view.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
                 conferenceNav.navigationBar.isTranslucent = false
                 navigationController?.present(conferenceNav, animated: true, completion: nil)
-            } else if  message["message_scope_id"] as? String == "18" {
+            } else if  message["message_scope_id"] as? String == MessageScope.FORM {
                 let formView = FormEditor()
                 let messageText =  message["message_text"]  as? String ?? ""
                 formView.jsonData = messageText
@@ -6173,7 +6173,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
         var textChat = (dataMessages[indexPath.row]["message_text"] as? String) ?? ""
         var messageRequestFriend: String!
         if dataMessages[indexPath.row]["attachment_flag"] as? String == "27" || dataMessages[indexPath.row]["attachment_flag"] as? String == "26" ||
-            dataMessages[indexPath.row]["attachment_flag"] as? String == "25" || dataMessages[indexPath.row]["message_scope_id"] as? String == "18" {
+            dataMessages[indexPath.row]["attachment_flag"] as? String == "25" || dataMessages[indexPath.row]["message_scope_id"] as? String == MessageScope.FORM {
             messageText.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 85).isActive = true
             let imageLS = UIImageView()
             containerMessage.addSubview(imageLS)
@@ -6190,7 +6190,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                 imageLS.image = UIImage(named: "pb_live_tv", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
             } else if dataMessages[indexPath.row]["attachment_flag"] as! String == "25" {
                 imageLS.image = UIImage(named: "pb_vroom", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
-            } else if dataMessages[indexPath.row]["message_scope_id"] as? String == "18" {
+            } else if dataMessages[indexPath.row]["message_scope_id"] as? String == MessageScope.FORM {
                 imageLS.image = UIImage(systemName: "doc.richtext.fill")
                 imageLS.tintColor = .mainColor
             }
@@ -6349,7 +6349,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                 }
                 imageSticker.image = imageStickerBundle //resourcesMediaBundle
                 imageSticker.contentMode = .scaleAspectFit
-            } else if dataMessages[indexPath.row]["message_scope_id"]  as? String ?? "" == "18" {
+            } else if dataMessages[indexPath.row]["message_scope_id"]  as? String ?? "" == MessageScope.FORM {
                 let data = textChat
                 if let jsonForm = try! JSONSerialization.jsonObject(with: data.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
                     let form_title = jsonForm["form_title"]  as? String ?? ""
@@ -6968,7 +6968,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
             }
         }
         
-        if (fileChat != "" && dataMessages[indexPath.row]["lock"]  as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
+        if (!fileChat.isEmpty && dataMessages[indexPath.row]["lock"]  as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
             topMarginText.constant = topMarginText.constant + 55
             
             let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
@@ -7262,11 +7262,9 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
             }
         }
         
-        if (reffChat != "" && dataMessages[indexPath.row]["message_scope_id"]  as? String ?? "" != "18") {
+        if (!reffChat.isEmpty && dataMessages[indexPath.row]["message_scope_id"]  as? String ?? "" != MessageScope.FORM) {
             let data = queryMessageReply(message_id: reffChat)
             if data.count != 0 {
-                topMarginText.constant = topMarginText.constant + 55
-                
                 let containerReply = UIView()
                 containerMessage.addSubview(containerReply)
                 containerReply.translatesAutoresizingMaskIntoConstraints = false
@@ -7284,7 +7282,9 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                     containerReply.bottomAnchor.constraint(equalTo: messageText.topAnchor, constant: -5).isActive = true
                 }
                 containerReply.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
-                containerReply.heightAnchor.constraint(equalToConstant: 50).isActive = true
+                let minHeightConstraint = containerReply.heightAnchor.constraint(greaterThanOrEqualToConstant: 50 + (self.offset()*3))
+                minHeightConstraint.priority = .defaultHigh
+                minHeightConstraint.isActive = true
                 containerReply.backgroundColor = .black.withAlphaComponent(0.2)
                 containerReply.layer.cornerRadius = 5
                 containerReply.clipsToBounds = true
@@ -7333,10 +7333,14 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                 }
                 
                 let contentReply = UILabel()
+                contentReply.numberOfLines = 2
                 containerReply.addSubview(contentReply)
                 contentReply.translatesAutoresizingMaskIntoConstraints = false
                 contentReply.leadingAnchor.constraint(equalTo: leftReply.leadingAnchor, constant: 10).isActive = true
                 contentReply.bottomAnchor.constraint(equalTo: containerReply.bottomAnchor, constant: -10).isActive = true
+                let topConstraintContent = contentReply.topAnchor.constraint(equalTo: titleReply.bottomAnchor)
+                topConstraintContent.priority = .defaultHigh
+                topConstraintContent.isActive = true
                 contentReply.font = UIFont.systemFont(ofSize: 10 + offset())
                 let message_text = data["message_text"]  as? String ?? ""
                 let attachment_flag = data["attachment_flag"]  as? String ?? ""
@@ -7348,13 +7352,13 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                     contentReply.trailingAnchor.constraint(equalTo: containerReply.trailingAnchor, constant: -20).isActive = true
                     contentReply.attributedText = message_text.richText()
                 } else if (attachment_flag == "1" || image_chat != "") {
-                    if (message_text == "") {
+                    if (message_text.trimmingCharacters(in: .whitespacesAndNewlines) == "") {
                         contentReply.text = "📷 Photo".localized()
                     } else {
                         contentReply.attributedText = message_text.richText()
                     }
                 } else if (attachment_flag == "2" || video_chat != "") {
-                    if (message_text == "") {
+                    if (message_text.trimmingCharacters(in: .whitespacesAndNewlines) == "") {
                         contentReply.text = "📹 Video".localized()
                     } else {
                         contentReply.attributedText = message_text.richText()
@@ -7473,7 +7477,16 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
             titleForwarded.attributedText = " $\(textForwarded)$".richText()
         }
         if messageText.isDescendant(of: containerMessage) {
-            topMarginText.isActive = true
+            var addTopMargin = true
+            if !reffChat.isEmpty && dataMessages[indexPath.row]["message_scope_id"]  as? String ?? "" != MessageScope.FORM {
+                let data = queryMessageReply(message_id: reffChat)
+                if data.count != 0 {
+                    addTopMargin = false
+                }
+            }
+            if addTopMargin{
+                topMarginText.isActive = true
+            }
         }
 //        let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(panGestureCellAction))
 //        panGestureRecognizer.delegate = self
@@ -8176,7 +8189,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
             return
         }
         UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
-            self.constraintTopTextField.constant = self.constraintTopTextField.constant + 50
+            self.constraintTopTextField.constant = self.constraintTopTextField.constant + 50 + (self.offset()*3)
         }, completion: nil)
         if (self.currentIndexpath != nil) {
             DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
@@ -8231,6 +8244,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
         self.containerPreviewReply.addSubview(contentReply)
         contentReply.translatesAutoresizingMaskIntoConstraints = false
         contentReply.leadingAnchor.constraint(equalTo: leftReply.leadingAnchor, constant: 10).isActive = true
+        contentReply.trailingAnchor.constraint(equalTo: containerPreviewReply.trailingAnchor, constant: -20).isActive = true
         contentReply.topAnchor.constraint(equalTo: titleReply.bottomAnchor).isActive = true
         contentReply.font = UIFont.systemFont(ofSize: 10 + offset())
         let message_text = dataMessages[indexPath.row]["message_text"]  as? String ?? ""
@@ -8242,13 +8256,13 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
         if (attachment_flag == "0" && thumb_chat == "") {
             contentReply.attributedText = message_text.richText()
         } else if (attachment_flag == "1" || image_chat != "") {
-            if (message_text == "") {
+            if (message_text.trimmingCharacters(in: .whitespacesAndNewlines) == "") {
                 contentReply.text = "📷 Photo".localized()
             } else {
                 contentReply.attributedText = message_text.richText()
             }
         } else if (attachment_flag == "2" || video_chat != "") {
-            if (message_text == "") {
+            if (message_text.trimmingCharacters(in: .whitespacesAndNewlines) == "") {
                 contentReply.text = "📹 Video".localized()
             } else {
                 contentReply.attributedText = message_text.richText()

+ 5 - 5
NexilisLite/NexilisLite/Source/View/Chat/EditorStarMessages.swift

@@ -359,7 +359,7 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
         if let attachmentFlag = dataMessages[indexPath.row]["attachment_flag"], let attachmentFlag = attachmentFlag as? String {
             if attachmentFlag == "27" || attachmentFlag == "26", let data = textChat { // live streaming
                 if let json = try! JSONSerialization.jsonObject(with: data.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
-                    Database().database?.inTransaction({ fmdb, rollback in
+                    Database.shared.database?.inTransaction({ fmdb, rollback in
                         let title = json["title"] as! String
                         let description = json["description"] as! String
                         let start = json["time"] as! Int64
@@ -452,7 +452,7 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
         let imageThumb = UIImageView()
         let containerViewFile = UIView()
         
-        if (thumbChat != "") {
+        if (!thumbChat.isEmpty) {
             topMarginText.constant = topMarginText.constant + 205
             
             containerMessage.addSubview(imageThumb)
@@ -544,7 +544,7 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
             objectTap.indexPath = indexPath
         }
         
-        if (fileChat != "") {
+        if (!fileChat.isEmpty) {
             topMarginText.constant = topMarginText.constant + 55
             
             let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
@@ -1293,7 +1293,7 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
     
     func getDataProfile(f_pin: String) -> [String: String]{
         var data: [String: String] = [:]
-        Database().database?.inTransaction({ fmdb, rollback in
+        Database.shared.database?.inTransaction({ fmdb, rollback in
             if let c = Database().getRecords(fmdb: fmdb, query: "select first_name || ' ' || last_name, image_id from BUDDY where f_pin = '\(f_pin)'"), c.next() {
                 data["name"] = c.string(forColumnIndex: 0)!.trimmingCharacters(in: .whitespacesAndNewlines)
                 data["image_id"] = c.string(forColumnIndex: 1)!
@@ -1313,7 +1313,7 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
     
     private func getDataProfileFromMessageId(message_id: String) -> [String: String]{
         var data: [String: String] = [:]
-        Database().database?.inTransaction({ fmdb, rollback in
+        Database.shared.database?.inTransaction({ fmdb, rollback in
             if let c = Database().getRecords(fmdb: fmdb, query: "select f_display_name from MESSAGE where message_id = '\(message_id)'"), c.next() {
                 data["name"] = c.string(forColumnIndex: 0)!
                 c.close()

+ 5 - 5
NexilisLite/NexilisLite/Source/View/Chat/MessageInfo.swift

@@ -63,7 +63,7 @@ class MessageInfo: UIViewController, UITableViewDelegate, UITableViewDataSource,
     private func getData() {
         Database.shared.database?.inTransaction({ (fmdb, rollback) in
             do {
-                if let cursorData = Database.shared.getRecords(fmdb: fmdb, query: "SELECT f_pin, status, time_delivered, time_read, time_ack, longitude, latitude FROM MESSAGE_STATUS where message_id='\(data["message_id"]!!)' ORDER BY time_delivered DESC, time_read DESC, time_ack DESC") {
+                if let cursorData = Database.shared.getRecords(fmdb: fmdb, query: "SELECT f_pin, status, time_delivered, time_read, time_ack, longitude, latitude FROM MESSAGE_STATUS where message_id='\(data["message_id"]!!)' ORDER BY time_ack DESC, time_read DESC, time_delivered DESC") {
                     var listStatus: [Int] = []
                     while cursorData.next() {
                         var row: [String: Any?] = [:]
@@ -685,7 +685,7 @@ class MessageInfo: UIViewController, UITableViewDelegate, UITableViewDataSource,
                 if attachmentFlag == "27" || attachmentFlag == "26" { // live streaming
                     let data = textChat
                     if let json = try! JSONSerialization.jsonObject(with: data.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
-                        Database().database?.inTransaction({ fmdb, rollback in
+                        Database.shared.database?.inTransaction({ fmdb, rollback in
                             let title = json["title"] as? String ?? ""
                             let description = json["description"] as? String ?? ""
                             let start = json["time"] as? Int64 ?? 0
@@ -707,7 +707,7 @@ class MessageInfo: UIViewController, UITableViewDelegate, UITableViewDataSource,
                 else if attachmentFlag == "25" {
                     let data = textChat
                     if let json = try! JSONSerialization.jsonObject(with: data.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
-                        Database().database?.inTransaction({ fmdb, rollback in
+                        Database.shared.database?.inTransaction({ fmdb, rollback in
                             var stringLS = ""
                             let title = json["title"] as? String ?? ""
                             let blog = json["blog"] as? String ?? ""
@@ -1219,7 +1219,7 @@ class MessageInfo: UIViewController, UITableViewDelegate, UITableViewDataSource,
     
     private func queryMessageReply(message_id: String) -> [String: Any?] {
         var dataQuery: [String: Any] = [:]
-        Database().database?.inTransaction({ fmdb, rollback in
+        Database.shared.database?.inTransaction({ fmdb, rollback in
             if let c = Database().getRecords(fmdb: fmdb, query: "SELECT message_id, f_pin, message_text, attachment_flag, thumb_id, image_id, video_id, file_id FROM MESSAGE where message_id='\(message_id)'"), c.next() {
                 dataQuery["message_id"] = c.string(forColumnIndex: 0)
                 dataQuery["f_pin"] = c.string(forColumnIndex: 1)
@@ -1237,7 +1237,7 @@ class MessageInfo: UIViewController, UITableViewDelegate, UITableViewDataSource,
     
     private func getDataProfile(f_pin: String, message_id: String) -> [String: String]{
         var data: [String: String] = [:]
-        Database().database?.inTransaction({ fmdb, rollback in
+        Database.shared.database?.inTransaction({ fmdb, rollback in
             if let c = Database().getRecords(fmdb: fmdb, query: "select first_name || ' ' || last_name, image_id from BUDDY where f_pin = '\(f_pin)'"), c.next() {
                 data["name"] = c.string(forColumnIndex: 0)!.trimmingCharacters(in: .whitespacesAndNewlines)
                 data["image_id"] = c.string(forColumnIndex: 1)!

+ 16 - 6
NexilisLite/NexilisLite/Source/View/Control/BackupRestoreView.swift

@@ -134,7 +134,7 @@ public class BackupRestoreView: UIViewController, UITableViewDataSource, UITable
             }
             return "\(User.getMyPin()!)_\(option)_\(fileId).zip"
         }
-        return "\(User.getMyPin()!).zip"
+        return "\(User.getMyPin()!)_\(option)_\(Date().currentTimeMillis())"
     }
     
     public func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
@@ -251,14 +251,23 @@ public class BackupRestoreView: UIViewController, UITableViewDataSource, UITable
                         
                     }
                 } else {
-                    Download().startHTTP(forKey: getFileName(option: optionBackup, fileId: fileIdBackup)) { (name, progress) in
+                    Download().startHTTP(forKey: getFileName(option: optionBackup, fileId: fileIdBackup), isBackup: true) { (name, progress) in
                         DispatchQueue.main.async { [self] in
                             guard progress == 100 else {
-                                labelRestoring.text = "Downloading...".localized() + "  \(progress)%"
+                                if progress != -100 {
+                                    labelRestoring.text = "Downloading...".localized() + "  \(progress)%"
+                                } else {
+                                    labelRestoring.text = "Failed Restored Data".localized()
+                                    DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
+                                        self.isRestoreStart = false
+                                        tableView.reloadRows(at: [indexPath, IndexPath(row: 1, section: 1), IndexPath(row: 0, section: 0), IndexPath(row: 0, section: 2)], with: .none)
+                                        tableView.reloadSections(IndexSet(integer: 3), with: .none)
+                                    })
+                                }
                                 return
                             }
                             labelRestoring.text = "Restoring...".localized()
-                            if FileEncryption.shared.isSecureExists(filename: getFileName(option: optionBackup, fileId: fileIdBackup)) {
+                            if FileEncryption.shared.isSecureExists(filename: fileIdBackup) {
                                 do {
                                     if var data = try FileEncryption.shared.readSecure(filename: getFileName(option: optionBackup, fileId: fileIdBackup)) {
                                         let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
@@ -875,7 +884,8 @@ public class BackupRestoreView: UIViewController, UITableViewDataSource, UITable
     //            let listFiles: [URL] = [file_message, file_uc_list, file_form_data, file_task_pic, file_task_detail]
                 do {
                     try fileManager.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil)
-                    let zipFiles = destinationURL.appendingPathComponent(getFileName(option: choosenOption, fileId: fileIdBackup, withoutZIP: true)).appendingPathExtension("zip")
+                    let nameZip = getFileName(option: choosenOption, fileId: "", withoutZIP: true)
+                    let zipFiles = destinationURL.appendingPathComponent(nameZip).appendingPathExtension("zip")
     //                try Zip.zipFiles(paths: listFiles, zipFilePath: zipFiles, password: nil, progress: {progress in
     //                    self.labelPreparing.text = "Preparing...".localized() + " \(progress * 100)%"
     //                })
@@ -911,7 +921,7 @@ public class BackupRestoreView: UIViewController, UITableViewDataSource, UITable
                                         let attrib = try FileManager.default.attributesOfItem(atPath: path)
                                         let fileSize = attrib[.size] as! Int64
                                         DispatchQueue.global().async { [self] in
-                                            _ = Nexilis.write(message: CoreMessage_TMessageBank.getBackupUploaded(option: choosenOption, fileid: fileIdBackup, filesize: String(fileSize), recordSize: String(recordSize)))
+                                            _ = Nexilis.write(message: CoreMessage_TMessageBank.getBackupUploaded(option: choosenOption, fileid: nameZip.components(separatedBy: "_")[2], filesize: String(fileSize), recordSize: String(recordSize)))
                                         }
                                         let date = Date()
                                         let calendar = Calendar.current

+ 31 - 15
NexilisLite/NexilisLite/Source/View/Control/ContactChatViewController.swift

@@ -48,7 +48,7 @@ class ContactChatViewController: UITableViewController {
     var noData = false
     
     var loadingData = true
-    var timerReloadData = Timer()
+    var timerReloadData: Timer?
     
     var noUCList = false
     
@@ -203,6 +203,7 @@ class ContactChatViewController: UITableViewController {
         NotificationCenter.default.addObserver(self, selector: #selector(onReloadTab(notification:)), name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil)
         NotificationCenter.default.addObserver(self, selector: #selector(onReload(notification:)), name: NSNotification.Name(rawValue: "onUpdatePersonInfo"), object: nil)
         NotificationCenter.default.addObserver(self, selector: #selector(onReloadTab(notification:)), name: NSNotification.Name(rawValue: "onTopic"), object: nil)
+        NotificationCenter.default.addObserver(self, selector: #selector(onDisconnected(notification:)), name: NSNotification.Name(rawValue: "disconnected_nexilis"), object: nil)
         
         tableView.tableHeaderView = segment
         tableView.tableFooterView = UIView()
@@ -265,11 +266,26 @@ class ContactChatViewController: UITableViewController {
 //    }
     
     private func reloadAllData() {
-        timerReloadData.invalidate()
-        timerReloadData = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: {_ in
-            self.getData()
-        })
-        timerReloadData.fire()
+        DispatchQueue.main.async { [weak self] in
+            self?.timerReloadData?.invalidate()
+            self?.timerReloadData = nil
+            self?.timerReloadData = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { [weak self] _ in
+                self?.getData()
+                self?.timerReloadData = nil
+            }
+        }
+    }
+    
+    @objc func onDisconnected(notification: NSNotification) {
+        DispatchQueue.main.async {
+            self.loadingData = true
+            self.chatGroupMaps.removeAll()
+            self.chats.removeAll()
+            self.contacts.removeAll()
+            self.groups.removeAll()
+            self.tableView.reloadData()
+            self.reloadAllData()
+        }
     }
     
     @objc func onReloadTab(notification: NSNotification) {
@@ -436,18 +452,18 @@ class ContactChatViewController: UITableViewController {
     
     private func getContacts(completion: @escaping ()->()) {
         self.contacts.removeAll()
-        if self.isChooser == nil {
-            let gptUser = User(pin: "-997",
-                            firstName: "GPT SmartBot",
-                            lastName: "",
-                            thumb: "",
-                            userType: "0",
-                            official: "1")
-            contacts.append(gptUser)
-        }
         DispatchQueue.global().async {
             Database.shared.database?.inTransaction({ (fmdb, rollback) in
                 do {
+                    if self.isChooser == nil {
+                        let gptUser = User(pin: "-997",
+                                        firstName: "GPT SmartBot",
+                                        lastName: "",
+                                        thumb: "",
+                                        userType: "0",
+                                        official: "1")
+                        self.contacts.append(gptUser)
+                    }
                     if let cursorData = Database.shared.getRecords(fmdb: fmdb, query: "SELECT f_pin, first_name, last_name, image_id, official_account, user_type FROM BUDDY where f_pin <> '\(User.getMyPin()!)' order by 5 desc, 2 collate nocase asc") {
                         while cursorData.next() {
                             let user = User(pin: cursorData.string(forColumnIndex: 0) ?? "",

+ 1 - 1
NexilisLite/NexilisLite/Source/View/Control/HistoryCCViewController.swift

@@ -165,7 +165,7 @@ public class HistoryCCViewController: UITableViewController, QLPreviewController
     
     func getDataProfile(f_pin: String) -> [String: String]{
         var data: [String: String] = [:]
-        Database().database?.inTransaction({ fmdb, rollback in
+        Database.shared.database?.inTransaction({ fmdb, rollback in
             if let c = Database().getRecords(fmdb: fmdb, query: "select first_name || ' ' || last_name, image_id from BUDDY where f_pin = '\(f_pin)'"), c.next() {
                 data["name"] = c.string(forColumnIndex: 0)!.trimmingCharacters(in: .whitespacesAndNewlines)
                 data["image"] = c.string(forColumnIndex: 1)!

+ 5 - 9
NexilisLite/NexilisLite/Source/View/Control/SettingTableViewController.swift

@@ -117,8 +117,7 @@ public class SettingTableViewController: UITableViewController, UIGestureRecogni
                                 Item(icon: UIImage(systemName: "person"), title: "Personal Information".localized()),
                                 Item(icon: UIImage(systemName: "textformat.abc"), title: "Change Language".localized()),
                                 Item(icon: UIImage(systemName: "textformat.size"), title: "Chat Font Size".localized()),
-                                Item(icon: UIImage(systemName: "photo"),
-                                     title: "Chat Wallpaper".localized()),
+//                                Item(icon: UIImage(systemName: "photo"), title: "Chat Wallpaper".localized()),
                                 Item(icon: UIImage(systemName: "lock"), title: "Secure Folder"),
 //                                Item(icon: UIImage(systemName: "person.crop.rectangle"), title: "Change Admin / Internal Password".localized()),
                                 Item(icon: UIImage(systemName: "laptopcomputer.and.iphone"), title: "Sign-In to Web".localized()),
@@ -130,8 +129,7 @@ public class SettingTableViewController: UITableViewController, UIGestureRecogni
                                 Item(icon: UIImage(systemName: "person"), title: "Personal Information".localized()),
                                 Item(icon: UIImage(systemName: "textformat.abc"), title: "Change Language".localized()),
                                 Item(icon: UIImage(systemName: "textformat.size"), title: "Chat Font Size".localized()),
-                                Item(icon: UIImage(systemName: "photo"),
-                                     title: "Chat Wallpaper".localized()),
+//                                Item(icon: UIImage(systemName: "photo"), title: "Chat Wallpaper".localized()),
                                 Item(icon: UIImage(systemName: "lock"), title: "Secure Folder"),
                                 Item(icon: UIImage(systemName: "laptopcomputer.and.iphone"), title: "Sign-In to Web".localized()),
                             ]
@@ -140,8 +138,7 @@ public class SettingTableViewController: UITableViewController, UIGestureRecogni
                                 Item(icon: UIImage(systemName: "person"), title: "Personal Information".localized()),
                                     Item(icon: UIImage(systemName: "textformat.abc"), title: "Change Language".localized()),
                                 Item(icon: UIImage(systemName: "textformat.size"), title: "Chat Font Size".localized()),
-                                Item(icon: UIImage(systemName: "photo"),
-                                     title: "Chat Wallpaper".localized()),
+//                                Item(icon: UIImage(systemName: "photo"), title: "Chat Wallpaper".localized()),
                                 Item(icon: UIImage(systemName: "lock"), title: "Secure Folder"),
 //                                Item(icon: UIImage(systemName: "person.badge.key"), title: "Access Admin / Internal Features".localized()),
                             ]
@@ -226,9 +223,8 @@ public class SettingTableViewController: UITableViewController, UIGestureRecogni
                             Item(icon: UIImage(systemName: "person"), title: "Personal Information".localized()),
                                 Item(icon: UIImage(systemName: "textformat.abc"), title: "Change Language".localized()),
                             Item(icon: UIImage(systemName: "textformat.size"), title: "Chat Font Size".localized()),
-                            Item(icon: UIImage(systemName: "photo"),
-                                 title: "Chat Wallpaper".localized()),
-                            Item(icon: UIImage(systemName: "lock"), title: "Secure Folder"),
+//                            Item(icon: UIImage(systemName: "photo"), title: "Chat Wallpaper".localized()),
+//                            Item(icon: UIImage(systemName: "lock"), title: "Secure Folder"),
 //                            Item(icon: UIImage(systemName: "person.badge.key"), title: "Access Admin / Internal Features".localized()),
                         ]
                         Item.menus["Personal"]?.append(Item(icon: UIImage(systemName: "arrow.up.and.person.rectangle.portrait"), title: "Sign-Up/Sign-In".localized()))

+ 1 - 1
NexilisLite/Podfile

@@ -8,7 +8,7 @@ target 'NexilisLite' do
   # Pods for NexilisLite
 
   pod 'nuSDKService', '4.0.22'
-  pod 'FMDB', '~> 2.7.12'
+  pod 'FMDB/SQLCipher', '~> 2.7.12'
   pod 'NotificationBannerSwift', :git => 'https://github.com/Daltron/NotificationBanner.git', :tag => '4.0.0'
   pod 'Alamofire', '~> 5.10.2'
   pod 'SDWebImage', '~> 5.20.0'