Ver Fonte

fix bugs and release for 5.0.51

alqindiirsyam há 1 mês atrás
pai
commit
b31fa9cc57

+ 9 - 5
AppBuilder/AppBuilder.xcodeproj/project.pbxproj

@@ -20,6 +20,7 @@
 		A42ED92427F3FC2F00B0FAB7 /* SecondTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A42ED92327F3FC2F00B0FAB7 /* SecondTabViewController.swift */; };
 		A42ED92627F439A200B0FAB7 /* ThirdTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A42ED92527F439A200B0FAB7 /* ThirdTabViewController.swift */; };
 		CD08A1682D5EEDA5005B4EAC /* AppBuilderShare.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = CD08A15E2D5EEDA5005B4EAC /* AppBuilderShare.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+		CD5D8BCF2E2754D100F56410 /* StatusUpdateVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD5D8BCE2E2754C100F56410 /* StatusUpdateVC.swift */; };
 		CD9D59D92BEE1D30008014B4 /* digisales_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = CD9D59CE2BEE1D2F008014B4 /* digisales_icon.png */; };
 		CD9D59DA2BEE1D30008014B4 /* ikn_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = CD9D59CF2BEE1D2F008014B4 /* ikn_icon.png */; };
 		CD9D59DB2BEE1D30008014B4 /* gudeg_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = CD9D59D02BEE1D2F008014B4 /* gudeg_icon.png */; };
@@ -88,6 +89,7 @@
 		A42ED92327F3FC2F00B0FAB7 /* SecondTabViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondTabViewController.swift; sourceTree = "<group>"; };
 		A42ED92527F439A200B0FAB7 /* ThirdTabViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdTabViewController.swift; sourceTree = "<group>"; };
 		CD08A15E2D5EEDA5005B4EAC /* AppBuilderShare.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = AppBuilderShare.appex; sourceTree = BUILT_PRODUCTS_DIR; };
+		CD5D8BCE2E2754C100F56410 /* StatusUpdateVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusUpdateVC.swift; sourceTree = "<group>"; };
 		CD9D59CE2BEE1D2F008014B4 /* digisales_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = digisales_icon.png; sourceTree = "<group>"; };
 		CD9D59CF2BEE1D2F008014B4 /* ikn_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ikn_icon.png; sourceTree = "<group>"; };
 		CD9D59D02BEE1D2F008014B4 /* gudeg_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = gudeg_icon.png; sourceTree = "<group>"; };
@@ -150,8 +152,8 @@
 			isa = PBXGroup;
 			children = (
 				CDE27BA42D53641D006298BD /* AppBuilder.entitlements */,
-				2401CEA7275490E600B323BB /* Info.plist */,
 				CDFD90D02E05361400F1E245 /* GoogleService-Info.plist */,
+				2401CEA7275490E600B323BB /* Info.plist */,
 				CD9D59D42BEE1D30008014B4 /* bi_icon.png */,
 				CD9D59D32BEE1D30008014B4 /* bpkh_icon.png */,
 				CD9D59D52BEE1D30008014B4 /* diginets_icon.png */,
@@ -169,6 +171,7 @@
 				A413B18627EACB20006D16EB /* PrefsUtil.swift */,
 				2401CE9B275490DB00B323BB /* SceneDelegate.swift */,
 				A42ED92327F3FC2F00B0FAB7 /* SecondTabViewController.swift */,
+				CD5D8BCE2E2754C100F56410 /* StatusUpdateVC.swift */,
 				A42ED92527F439A200B0FAB7 /* ThirdTabViewController.swift */,
 				2401CE9D275490DB00B323BB /* ViewController.swift */,
 				2401CEA2275490E600B323BB /* Assets.xcassets */,
@@ -374,6 +377,7 @@
 				A42ED92627F439A200B0FAB7 /* ThirdTabViewController.swift in Sources */,
 				2401CE9E275490DB00B323BB /* ViewController.swift in Sources */,
 				2401CE9A275490DB00B323BB /* AppDelegate.swift in Sources */,
+				CD5D8BCF2E2754D100F56410 /* StatusUpdateVC.swift in Sources */,
 				12960AE02892361000A467DD /* FourthTabViewController.swift in Sources */,
 				2401CE9C275490DB00B323BB /* SceneDelegate.swift in Sources */,
 			);
@@ -564,7 +568,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 5.0.50;
+				MARKETING_VERSION = 5.0.51;
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
@@ -600,7 +604,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 5.0.50;
+				MARKETING_VERSION = 5.0.51;
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
@@ -636,7 +640,7 @@
 					"@executable_path/../../Frameworks",
 				);
 				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
-				MARKETING_VERSION = 5.0.50;
+				MARKETING_VERSION = 5.0.51;
 				OTHER_CFLAGS = "-fstack-protector-strong";
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder.AppBuilderShare;
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -675,7 +679,7 @@
 					"@executable_path/../../Frameworks",
 				);
 				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
-				MARKETING_VERSION = 5.0.50;
+				MARKETING_VERSION = 5.0.51;
 				OTHER_CFLAGS = "-fstack-protector-strong";
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder.AppBuilderShare;
 				PRODUCT_NAME = "$(TARGET_NAME)";

+ 17 - 2
AppBuilder/AppBuilder/FourthTabViewController.swift

@@ -264,9 +264,10 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
         
         Item.menus["Config"] = [
             Item(icon: UIImage(systemName: "iphone"), title: "Create Your Own App".localized()),
-            Item(icon: UIImage(systemName: "gearshape.circle"), title: "Configure Floating Button".localized())
+            Item(icon: UIImage(systemName: "gearshape.circle"), title: "Configure Floating Button".localized()),
+            Item(icon: UIImage(systemName: "paintbrush"), title: "Switch Style".localized())
         ]
-        if !isChangeProfile || Utils.getEnableMobileBuilder() != "1" {
+        if !isChangeProfile || !Nexilis.checkingAccess(key: "create_miniapp") {
             if Item.menus["Config"]!.count > 1 {
                 Item.menus["Config"]!.remove(at: 0)
             } else {
@@ -280,6 +281,13 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
                 Item.menus["Config"]!.removeAll()
             }
         }
+        if !isChangeProfile || !Nexilis.checkingAccess(key: "can_switch_style") {
+            if Item.menus["Config"]!.count > 1 {
+                Item.menus["Config"]!.remove(at: 1)
+            } else {
+                Item.menus["Config"]!.removeAll()
+            }
+        }
         if Utils.getIsLoadThemeFromOther() {
             Item.menus["Config"]?.insert(Item(icon: UIImage(systemName: "iphone"), title: "Back to Company App".localized()), at: 1)
         }
@@ -520,6 +528,8 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
                 cell.accessoryType = .disclosureIndicator
             case "Create Your Own App".localized():
                 cell.accessoryType = .disclosureIndicator
+            case "Switch Style".localized():
+                cell.accessoryType = .disclosureIndicator
             case "Notification Message(s) Group".localized():
                 cell.accessoryType = .disclosureIndicator
             case "Change Admin / Internal Password".localized():
@@ -919,6 +929,7 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
                                 Utils.setIsLoadThemeFromOther(value: false)
                                 Utils.resetValueSuperApp()
                                 Utils.setValueInitialApp(data: Utils.getPrefTheme())
+                                Utils.setLastTabSelected(value: 0)
                                 UIApplication.shared.setAlternateIconName(nil)
                                 Database.shared.database?.inTransaction({ fmdb, rollback in
                                     _ = Database.shared.deleteRecord(fmdb: fmdb, table: "GROUPZ", _where: "")
@@ -966,6 +977,10 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
             let alert = LibAlertController(title: "Version".localized(), message: API.sGetVersion() + "\nConnection: \(API.nGetCLXConnState() == 1)", preferredStyle: .alert)
             alert.addAction(UIAlertAction(title: "Dismiss".localized(), style: UIAlertAction.Style.default, handler: nil))
             self.present(alert, animated: true, completion: nil)
+        } else if item.title == "Switch Style".localized() {
+            let controller = BNIBookingWebView()
+            controller.customUrl = PrefsUtil.getURLBase() + "default_mobile_theme?f_pin="
+            self.present(controller, animated: true)
         }
     }
     

+ 5 - 0
AppBuilder/AppBuilder/PrefsUtil.swift

@@ -48,6 +48,11 @@ class PrefsUtil {
         let value: String? = SecureUserDefaults.shared.value(forKey: "app_builder_url_third_tab")
         return value
     }
+    
+    static func getURLStatusUpdate() -> String? {
+        let value: String? = SecureUserDefaults.shared.value(forKey: "app_builder_url_status_update")
+        return value
+    }
     static func getURLBase() -> String {
         let value: String = SecureUserDefaults.shared.value(forKey: "app_builder_url_base") ?? "https://nexilis.io/"
         return value

+ 872 - 0
AppBuilder/AppBuilder/StatusUpdateVC.swift

@@ -0,0 +1,872 @@
+//
+//  StatusUpdateVC.swift
+//  AppBuilder
+//
+//  Created by Qindi on 16/07/25.
+//
+import UIKit
+import WebKit
+import NexilisLite
+import Speech
+import CommonCrypto
+
+class StatusUpdateVC: UIViewController, UIScrollViewDelegate, UIGestureRecognizerDelegate, WKScriptMessageHandler, ImageVideoPickerDelegate {
+    
+    var webView: WKWebView!
+    var address = ""
+    private var lastContentOffset: CGFloat = 0
+    
+    var isAllowSpeech = false
+    
+    let speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "id"))
+
+    var recognitionRequest : SFSpeechAudioBufferRecognitionRequest?
+    var recognitionTask : SFSpeechRecognitionTask?
+    let audioEngine = AVAudioEngine()
+    var alertController = LibAlertController()
+    
+    public static var forceRefresh = true
+    public static var canLoadURL = false
+    public static var showModal = false
+    
+    var indexImageVideoWv = 0
+    var imageVideoPicker: ImageVideoPicker!
+    var blockedCertificate = ""
+    var allowedURLs = Set<String>()
+    var loadingURL = false
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        
+        self.view.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .black : .white
+
+        let configuration = WKWebViewConfiguration()
+        configuration.allowsInlineMediaPlayback = true
+        loadContentBlocker(into: configuration) { [self] in
+            DispatchQueue.main.async {
+                self.initializeWebView(with: configuration)
+            }
+        }
+    }
+    
+    func initializeWebView(with configuration: WKWebViewConfiguration) {
+        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(collapseDocked))
+        tapGesture.cancelsTouchesInView = false
+        tapGesture.delegate = self
+        let customUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Mobile/15E148 Safari/604.1 \(Utils.getUserAgent())"
+        let finalUserAgent = "\(customUserAgent)"
+        configuration.applicationNameForUserAgent = finalUserAgent
+        webView = WKWebView(frame: .zero, configuration: configuration)
+        view.addSubview(webView)
+        webView.translatesAutoresizingMaskIntoConstraints = false
+        NSLayoutConstraint.activate([
+            webView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
+            webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+            webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+            webView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
+        ])
+        webView.scrollView.addGestureRecognizer(tapGesture)
+        let refreshControl = UIRefreshControl()
+        refreshControl.addTarget(self, action: #selector(reloadWebView(_:)), for: .valueChanged)
+        webView.scrollView.addSubview(refreshControl)
+        webView.scrollView.delegate = self
+        webView.navigationDelegate = self
+        webView.allowsBackForwardNavigationGestures = true
+        
+        let contentController = self.webView.configuration.userContentController
+        contentController.add(self, name: "checkProfile")
+        contentController.add(self, name: "setIsProductModalOpen")
+        contentController.add(self, name: "toggleVoiceSearch")
+        contentController.add(self, name: "blockUser")
+        contentController.add(self, name: "showAlert")
+        contentController.add(self, name: "closeProfile")
+        contentController.add(self, name: "tabShowHide")
+        contentController.add(self, name: "shareText")
+        contentController.add(self, name: "openGalleryiOS")
+        
+        let source: String = "var meta = document.createElement('meta');" +
+            "meta.name = 'viewport';" +
+            "meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no';" +
+            "var head = document.getElementsByTagName('head')[0];" +
+            "head.appendChild(meta);" +
+        "$('#header-layout').find('.col-8').removeClass('col-8').addClass('col');" +
+        "$('#header-layout').find('.col-4').removeClass('col-4').addClass('col');"
+        let script: WKUserScript = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: false)
+        contentController.addUserScript(script)
+        
+        let cookieScript1 = "document.cookie = '\(Utils.getCookiesMobile().components(separatedBy: ";")[0])';"
+        let cookieScriptInjection1 = WKUserScript(source: cookieScript1, injectionTime: .atDocumentStart, forMainFrameOnly: false)
+        configuration.userContentController.addUserScript(cookieScriptInjection1)
+        
+        let cookieScript2 = "document.cookie = '\(Utils.getCookiesMobile().components(separatedBy: ";")[1])';"
+        let cookieScriptInjection2 = WKUserScript(source: cookieScript2, injectionTime: .atDocumentStart, forMainFrameOnly: false)
+        configuration.userContentController.addUserScript(cookieScriptInjection2)
+        
+        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)
+        StatusUpdateVC.canLoadURL = true
+        processURL()
+    }
+    
+    func loadContentBlocker(into config: WKWebViewConfiguration, completion: @escaping () -> Void) {
+        // Define ad-blocking rules directly in Swift as a string
+        let contentRules = PrefsUtil.contentRulesAds
+
+        WKContentRuleListStore.default().compileContentRuleList(forIdentifier: "AdBlocker", encodedContentRuleList: contentRules) { ruleList, error in
+            if let ruleList = ruleList {
+                config.userContentController.add(ruleList)
+            } else {
+                print("Failed to compile content rule list: \(error?.localizedDescription ?? "Unknown error")")
+            }
+            completion()
+        }
+    }
+    
+    func loadURLWithCookie(url: URL) {
+        var urlRequest = URLRequest(url: url)
+        let customUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Mobile/15E148 Safari/604.1 \(Utils.getUserAgent())"
+        urlRequest.setValue(customUserAgent, forHTTPHeaderField: "User-Agent")
+        if let cookies = HTTPCookieStorage.shared.cookies(for: url) {
+            for cookie in cookies {
+                webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)
+            }
+            webView.load(urlRequest)
+        }
+    }
+    
+    func processURL() {
+        let me = User.getMyPin()
+        
+        var myURL : URL?
+        let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
+        var intLang = 0
+        if lang == "id" {
+            intLang = 1
+        }
+        if PrefsUtil.getURLStatusUpdate() != nil {
+            ViewController.sURL = PrefsUtil.getURLStatusUpdate()!
+        }
+        switch(ViewController.sURL){
+        case "0":
+            address = "\(PrefsUtil.getURLBase())nexilis/pages/tab1-main-only?f_pin=\(me ?? "")&lang=\(intLang)&theme=\(self.traitCollection.userInterfaceStyle == .dark ? "0" : "1")"
+            myURL = URL(string: address)
+        case "1":
+            address = "\(PrefsUtil.getURLBase())nexilis/pages/tab3-main-only?f_pin=\(me ?? "")&lang=\(intLang)&theme=\(self.traitCollection.userInterfaceStyle == .dark ? "0" : "1")"
+            myURL = URL(string: address)
+        case "2":
+            address = "\(PrefsUtil.getURLBase())nexilis/pages/tab1-main?f_pin=\(me ?? "")&lang=\(intLang)&theme=\(self.traitCollection.userInterfaceStyle == .dark ? "0" : "1")"
+            myURL = URL(string: address)
+        case "3":
+            address = "\(PrefsUtil.getURLBase())nexilis/pages/tab3-commerce?f_pin=\(me ?? "")&lang=\(intLang)&theme=\(self.traitCollection.userInterfaceStyle == .dark ? "0" : "1")"
+            myURL = URL(string: address)
+        case "4":
+            address = "\(PrefsUtil.getURLBase())nexilis/pages/tab1-video?f_pin=\(me ?? "")&lang=\(intLang)&theme=\(self.traitCollection.userInterfaceStyle == .dark ? "0" : "1")"
+            myURL = URL(string: address)
+        default:
+            if(!ViewController.sURL.isEmpty){
+                if ViewController.sURL.contains("?f_pin="){
+                    address = "\(ViewController.sURL)\(me ?? "")"
+                } else {
+                    address = "\(ViewController.sURL)?f_pin=\(me ?? "")"
+                }
+                myURL = URL(string: address)
+            }
+        }
+        if let u = myURL {
+            webView.evaluateJavaScript("{window.localStorage.setItem('currentTab','\(ViewController.sURL)')}")
+            if StatusUpdateVC.forceRefresh {
+                loadURLWithCookie(url: u)
+            } else {
+                webView.evaluateJavaScript("if(resumeAll){resumeAll();}")
+            }
+            StatusUpdateVC.forceRefresh = false
+        }
+    }
+    
+    override func viewWillAppear(_ animated: Bool) {
+        if StatusUpdateVC.canLoadURL {
+            processURL()
+        }
+        navigationController?.setNavigationBarHidden(true, animated: false)
+    }
+    
+    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
+        if Utils.getIsLoadThemeFromOther() {
+            self.webView.evaluateJavaScript("{window.localStorage.setItem('mobileConfiguration','"+Utils.getMyTheme()+"')}")
+        }
+    }
+    
+    override func viewDidAppear(_ animated: Bool) {
+        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: {
+            var viewController = UIApplication.shared.windows.first!.rootViewController
+            if let nc = viewController as? UINavigationController {
+                viewController = nc.viewControllers.first
+            }
+            if ViewController.middleButton.isHidden {
+                ViewController.isExpandButton = false
+                if let viewController = viewController as? ViewController {
+                    if viewController.tabBar.isHidden {
+                        viewController.tabBar.isHidden = false
+                        ViewController.middleButton.isHidden = false
+                        ViewController.alwaysHideButton = false
+                    }
+                }
+            } else if PrefsUtil.getCpaasMode() != PrefsUtil.CPAAS_MODE_DOCKED {
+                DispatchQueue.main.async {
+                    if let viewController = viewController as? ViewController {
+                        if viewController.tabBar.isHidden {
+                            viewController.tabBar.isHidden = false
+                            ViewController.alwaysHideButton = false
+                        }
+                    }
+                }
+            }
+        })
+    }
+    
+    @objc func onShowAC(notification: NSNotification) {
+        self.webView.evaluateJavaScript("{if(pauseAll){pauseAll();}}")
+        view.endEditing(true)
+        resignFirstResponder()
+    }
+    
+    @objc func onRefreshWebView(notification: NSNotification) {
+        StatusUpdateVC.forceRefresh = true
+        StatusUpdateVC.showModal = false
+    }
+    
+    @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
+                self.webView.scrollView.setContentOffset(CGPoint.zero, animated: true)
+            }
+            self.webView.evaluateJavaScript("{if(pauseAll){pauseAll();}}")
+            self.webView.evaluateJavaScript("hideAddToCart();")
+        }
+    }
+    
+    func scrollViewDidScroll(_ scrollView: UIScrollView) {
+        if (self.lastContentOffset > scrollView.contentOffset.y && scrollView.contentOffset.y < (scrollView.contentSize.height - scrollView.frame.size.height)) {
+            showTabBar();
+        }
+        else if (self.lastContentOffset != 0 && self.lastContentOffset < scrollView.contentOffset.y && self.lastContentOffset >= 0) {
+            hideTabBar();
+        }
+        self.lastContentOffset = scrollView.contentOffset.y
+        self.collapseDocked()
+    }
+    
+    @objc func collapseDocked() {
+        if ViewController.isExpandButton {
+            ViewController.expandButton()
+        }
+    }
+
+    @objc func reloadWebView(_ sender: UIRefreshControl) {
+//        StatusUpdateVC.forceRefresh = true
+//        viewWillAppear(false)
+        webView.reload()
+        ViewController.alwaysHideButton = false
+        showTabBar()
+        sender.endRefreshing()
+    }
+    
+    func hideTabBar() {
+        if UIApplication.shared.windows.first == nil {
+            return
+        }
+        var viewController = UIApplication.shared.windows.first!.rootViewController
+        if let nc = viewController as? UINavigationController {
+            viewController = nc.viewControllers.first
+        }
+        if ViewController.middleButton.isDescendant(of: viewController!.view) {
+            DispatchQueue.main.async {
+                if ViewController.isExpandButton {
+                    ViewController.expandButton()
+                }
+                ViewController.hideDockedButton()
+                if let viewController = viewController as? ViewController {
+                    viewController.tabBar.isHidden = true
+                }
+                ViewController.removeMiddleButton()
+            }
+        } else if PrefsUtil.getCpaasMode() != PrefsUtil.CPAAS_MODE_DOCKED {
+            DispatchQueue.main.async {
+                if let viewController = viewController as? ViewController {
+                    if !viewController.tabBar.isHidden {
+                        viewController.tabBar.isHidden = true
+                    }
+                }
+            }
+        }
+    }
+
+    func showTabBar() {
+        if(ViewController.alwaysHideButton){
+            return
+        }
+        var viewController = UIApplication.shared.windows.first!.rootViewController
+        if let nc = viewController as? UINavigationController {
+            viewController = nc.viewControllers.first
+        }
+        if ViewController.middleButton.isHidden {
+            if let viewController = viewController as? ViewController {
+                viewController.tabBar.isHidden = false
+                ViewController.middleButton.isHidden = false
+            }
+        } else if PrefsUtil.getCpaasMode() != PrefsUtil.CPAAS_MODE_DOCKED {
+            DispatchQueue.main.async {
+                if let viewController = viewController as? ViewController {
+                    if viewController.tabBar.isHidden {
+                        viewController.tabBar.isHidden = false
+                    }
+                }
+            }
+        }
+    }
+
+    func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
+        scrollView.pinchGestureRecognizer?.isEnabled = false
+    }
+
+    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
+        if message.name == "checkProfile" {
+            guard let dict = message.body as? [String: AnyObject],
+                  let param1 = dict["param1"] as? String,
+                  let param2 = dict["param2"] as? String else {
+                return
+            }
+            if ViewController.checkIsChangePerson() {
+                if param2 == "like" {
+                    self.webView.evaluateJavaScript("likeProduct('\(param1)',1,true);")
+                } else if param2 == "comment" {
+                    self.webView.evaluateJavaScript("openComment('\(param1.split(separator: "|")[0])',\(param1.split(separator: "|")[1]),true);")
+                } else if param2 == "report_user" {
+                    self.webView.evaluateJavaScript("reportUser('\(param1)',true);")
+                } else if param2 == "report_content" {
+                    self.webView.evaluateJavaScript("reportContent('\(param1.split(separator: "|")[0])','\(param1.split(separator: "|")[1])',true);")
+                } else if param2 == "block_user" {
+                    self.webView.evaluateJavaScript("blockUser('\(param1)',true);")
+                } else if param2 == "follow_user" {
+                    self.webView.evaluateJavaScript("followUser('\(param1.split(separator: "|")[0])',\(param1.split(separator: "|")[1]),true);")
+                } else if param2 == "homepage" || param2 == "gif" {
+                    self.webView.evaluateJavaScript("window.location.href = '\(param1)';")
+                } else if param2 == "block_content" {
+                    self.webView.evaluateJavaScript("blockContent('\(param1)',true);")
+                } else {
+                    self.webView.evaluateJavaScript("openNewPost(true);")
+                }
+            } else {
+                self.webView.evaluateJavaScript("{if(pauseAll){pauseAll();}}")
+            }
+        } else if message.name == "setIsProductModalOpen" {
+            guard let dict = message.body as? [String: AnyObject],
+                  let param1 = dict["param1"] as? Bool else {
+                return
+            }
+            if param1 {
+                if self.webView.scrollView.contentOffset.y < 0 { // Move tableView to top
+                    self.webView.scrollView.setContentOffset(CGPoint.zero, animated: true)
+                }
+            }
+            StatusUpdateVC.showModal = param1
+        } else if message.name == "toggleVoiceSearch" {
+            if !isAllowSpeech {
+                setupSpeech()
+            } else {
+                runVoice()
+            }
+        } else if message.name == "blockUser" {
+            guard let dict = message.body as? [String: AnyObject],
+                  let param1 = dict["param1"] as? String,
+                  let param2 = dict["param2"] as? Bool else {
+                return
+            }
+            if param2 {
+                DispatchQueue.global().async {
+                    if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getBlock(l_pin: param1)) {
+                        if response.isOk() {
+                            DispatchQueue.main.async {
+                                Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                                    _ = Database.shared.updateRecord(fmdb: fmdb, table: "BUDDY", cvalues: [
+                                        "ex_block" : "1"
+                                    ], _where: "f_pin = '\(param1)'")
+                                })
+                            }
+                        }
+                    }
+                }
+            } else {
+                DispatchQueue.global().async {
+                    if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getUnBlock(l_pin: param1)) {
+                        if response.isOk() {
+                            DispatchQueue.main.async {
+                                Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                                    _ = Database.shared.updateRecord(fmdb: fmdb, table: "BUDDY", cvalues: [
+                                        "ex_block" : "0"
+                                    ], _where: "f_pin = '\(param1)'")
+                                })
+                            }
+                        }
+                    }
+                }
+            }
+        } else if message.name == "showAlert" {
+            guard let dict = message.body as? [String: AnyObject],
+                  let param1 = dict["param1"] as? String else {
+                return
+            }
+            self.view.makeToast(param1, duration: 3)
+        } else if message.name == "blockUser" {
+            guard let dict = message.body as? [String: AnyObject],
+                  let param1 = dict["param1"] as? String,
+                  let param2 = dict["param2"] as? Bool else {
+                return
+            }
+            if param2 {
+                DispatchQueue.global().async {
+                    if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getBlock(l_pin: param1)) {
+                        if response.isOk() {
+                            DispatchQueue.main.async {
+                                Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                                    _ = Database.shared.updateRecord(fmdb: fmdb, table: "BUDDY", cvalues: [
+                                        "ex_block" : "1"
+                                    ], _where: "f_pin = '\(param1)'")
+                                })
+                            }
+                        }
+                    }
+                }
+            } else {
+                DispatchQueue.global().async {
+                    if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getUnBlock(l_pin: param1)) {
+                        if response.isOk() {
+                            DispatchQueue.main.async {
+                                Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                                    _ = Database.shared.updateRecord(fmdb: fmdb, table: "BUDDY", cvalues: [
+                                        "ex_block" : "0"
+                                    ], _where: "f_pin = '\(param1)'")
+                                })
+                            }
+                        }
+                    }
+                }
+            }
+        } else if message.name == "tabShowHide" {
+            guard let dict = message.body as? [String: AnyObject],
+                  let param1 = dict["param1"] as? Bool else {
+                return
+            }
+            if param1 {
+                ViewController.alwaysHideButton = false
+                showTabBar()
+            } else {
+                if self.viewIfLoaded?.window != nil {
+                    ViewController.alwaysHideButton = true
+                    hideTabBar()
+                }
+            }
+        } else if message.name == "shareText" {
+            guard let dict = message.body as? [String: AnyObject],
+                  let param1 = dict["param1"] as? String else {
+                return
+            }
+            if loadingURL {
+                return
+            }
+            let activityViewController = UIActivityViewController(activityItems: [param1], applicationActivities: nil)
+            self.present(activityViewController, animated: true, completion: nil)
+        } else if message.name == "openGalleryiOS" {
+            guard let dict = message.body as? [String: AnyObject],
+                  let param1 = dict["param1"] as? Int else {
+                return
+            }
+            indexImageVideoWv = param1
+            let alertController = LibAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
+            
+            if let action = self.actionImageVideo(for: "image", title: "Choose Photo".localized()) {
+                alertController.addAction(action)
+            }
+            if let action = self.actionImageVideo(for: "video", title: "Choose Video".localized()) {
+                alertController.addAction(action)
+            }
+            alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: nil))
+            self.present(alertController, animated: true)
+        }
+    }
+    
+    private func actionImageVideo(for type: String, title: String) -> UIAlertAction? {
+        return UIAlertAction(title: title, style: .default) { [unowned self] _ in
+            switch type {
+            case "image":
+                imageVideoPicker.present(source: .imageAlbum)
+            case "video":
+                imageVideoPicker.present(source: .videoAlbum)
+            default:
+                imageVideoPicker.present(source: .imageAlbum)
+            }
+        }
+    }
+    
+    func didSelect(imagevideo: Any?) {
+        if imagevideo != nil {
+            let imageData = imagevideo! as! [UIImagePickerController.InfoKey : Any]
+            if (imageData[.mediaType] as! String == "public.image") {
+                let compressedImage = (imageData[.originalImage] as! UIImage).pngData()!
+                let base64String = compressedImage.base64EncodedString()
+                let base64ToWeb = "data:image/jpeg;base64,\(base64String)"
+                webView.evaluateJavaScript("loadFromMobile('\(base64ToWeb)',\(indexImageVideoWv))") { (result, error) in
+                    if let error = error {
+                        print("Error executing JavaScript: \(error)")
+                    }
+                }
+            } else {
+                guard var dataVideo = try? Data(contentsOf: imageData[.mediaURL] as! URL) else {
+                    return
+                }
+                let sizeOfVideo = Double(dataVideo.count / 1048576)
+                if (sizeOfVideo > 10.0) {
+                    let compressedURL = NSURL.fileURL(withPath: NSTemporaryDirectory() + UUID().uuidString + ".mp4")
+                    compressVideo(inputURL: imageData[.mediaURL] as! URL,
+                                  outputURL: compressedURL) { exportSession in
+                        guard let session = exportSession else {
+                            return
+                        }
+                        
+                        switch session.status {
+                        case .unknown:
+                            break
+                        case .waiting:
+                            break
+                        case .exporting:
+                            break
+                        case .completed:
+                            guard let compressedData = try? Data(contentsOf: compressedURL) else {
+                                return
+                            }
+                            dataVideo = compressedData
+                        case .failed:
+                            break
+                        case .cancelled:
+                            break
+                        @unknown default:
+                            break
+                        }
+                    }
+                }
+                let base64String = dataVideo.base64EncodedString()
+                let base64ToWeb = "data:video/mp4;base64,\(base64String)"
+                webView.evaluateJavaScript("loadFromMobile('\(base64ToWeb)',\(indexImageVideoWv))") { (result, error) in
+                    if let error = error {
+                        print("Error executing JavaScript: \(error)")
+                    }
+                }
+            }
+        }
+    }
+    
+    func compressVideo(inputURL: URL,
+                       outputURL: URL,
+                       handler:@escaping (_ exportSession: AVAssetExportSession?) -> Void) {
+        let urlAsset = AVURLAsset(url: inputURL, options: nil)
+        guard let exportSession = AVAssetExportSession(asset: urlAsset,
+                                                       presetName: AVAssetExportPresetMediumQuality) else {
+            handler(nil)
+            
+            return
+        }
+        
+        exportSession.outputURL = outputURL
+        exportSession.outputFileType = .mp4
+        exportSession.exportAsynchronously {
+            handler(exportSession)
+        }
+    }
+    
+    func setupSpeech() {
+
+        self.speechRecognizer?.delegate = self
+
+        SFSpeechRecognizer.requestAuthorization { (authStatus) in
+
+            var isButtonEnabled = false
+
+            switch authStatus {
+            case .authorized:
+                isButtonEnabled = true
+
+            case .denied:
+                isButtonEnabled = false
+                //print("User denied access to speech recognition")
+
+            case .restricted:
+                isButtonEnabled = false
+                //print("Speech recognition restricted on this device")
+
+            case .notDetermined:
+                isButtonEnabled = false
+                //print("Speech recognition not yet authorized")
+            @unknown default:
+                isButtonEnabled = false
+            }
+
+            OperationQueue.main.addOperation() {
+                self.isAllowSpeech = isButtonEnabled
+                if isButtonEnabled {
+                    SecureUserDefaults.shared.set(isButtonEnabled, forKey: "allowSpeech")
+                    self.runVoice()
+                }
+            }
+        }
+    }
+    
+    func runVoice() {
+        if !audioEngine.isRunning {
+            alertController = LibAlertController(title: "Start Recording".localized(), message: "Say something, I'm listening!".localized(), preferredStyle: .alert)
+            self.present(alertController, animated: true)
+            self.webView.evaluateJavaScript("toggleVoiceButton(true)")
+            self.startRecording()
+        }
+    }
+    
+    func startRecording() {
+
+        // Clear all previous session data and cancel task
+        if recognitionTask != nil {
+            recognitionTask?.cancel()
+            recognitionTask = nil
+        }
+
+        // Create instance of audio session to record voice
+        let audioSession = AVAudioSession.sharedInstance()
+        do {
+            try audioSession.setCategory(AVAudioSession.Category.record, mode: .default, options: [])
+            try audioSession.setMode(AVAudioSession.Mode.measurement)
+            try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
+        } catch {
+            //print("audioSession properties weren't set because of an error.")
+        }
+
+        self.recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
+
+        let inputNode = audioEngine.inputNode
+
+        guard let recognitionRequest = recognitionRequest else {
+            fatalError("Unable to create an SFSpeechAudioBufferRecognitionRequest object")
+        }
+
+        recognitionRequest.shouldReportPartialResults = true
+
+        self.recognitionTask = speechRecognizer?.recognitionTask(with: recognitionRequest, resultHandler: { (result, error) in
+
+            var isFinal = false
+            var text = ""
+
+            if result != nil {
+                text = result?.bestTranscription.formattedString ?? ""
+                isFinal = (result?.isFinal)!
+                self.alertController.dismiss(animated: true)
+                self.audioEngine.stop()
+                self.recognitionRequest?.endAudio()
+            } else {
+                self.alertController.dismiss(animated: true)
+            }
+
+            if error != nil || isFinal {
+                if error == nil {
+                    self.webView.evaluateJavaScript("toggleVoiceButton(false)")
+                    self.webView.evaluateJavaScript("submitVoiceSearch('\(text)')")
+                } else {
+                    self.audioEngine.stop()
+                    self.recognitionRequest?.endAudio()
+                }
+                inputNode.removeTap(onBus: 0)
+
+                self.recognitionRequest = nil
+                self.recognitionTask = nil
+
+                self.isAllowSpeech = true
+            }
+        })
+
+        let recordingFormat = inputNode.outputFormat(forBus: 0)
+        inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer, when) in
+            self.recognitionRequest?.append(buffer)
+        }
+
+        self.audioEngine.prepare()
+
+        do {
+            try self.audioEngine.start()
+        } catch {
+            //print("audioEngine couldn't start because of an error.")
+        }
+    }
+    
+    func isUsingMyWebview() -> Bool{
+        return PrefsUtil.getURLStatusUpdate() == "0" || PrefsUtil.getURLStatusUpdate() == "1" || PrefsUtil.getURLStatusUpdate() == "2" || PrefsUtil.getURLStatusUpdate() == "3" || PrefsUtil.getURLStatusUpdate() == "4"
+    }
+
+}
+
+extension StatusUpdateVC: SFSpeechRecognizerDelegate {
+
+    func speechRecognizer(_ speechRecognizer: SFSpeechRecognizer, availabilityDidChange available: Bool) {
+        if available {
+            self.isAllowSpeech = true
+        } else {
+            self.isAllowSpeech = false
+        }
+    }
+}
+
+extension StatusUpdateVC: WKUIDelegate, WKNavigationDelegate {
+    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping @MainActor (WKNavigationActionPolicy) -> Void) {
+        guard let url = navigationAction.request.url else {
+            decisionHandler(.cancel)
+            return
+        }
+        
+        if let scheme = url.scheme?.lowercased(), scheme.hasPrefix("itms") || scheme == "mailto" || scheme == "tel" {
+            DispatchQueue.main.async {
+                UIApplication.shared.open(url, options: [:], completionHandler: nil)
+            }
+            decisionHandler(.cancel)
+            return
+        }
+
+        guard navigationAction.targetFrame?.isMainFrame == true else {
+            decisionHandler(.allow)
+            return
+        }
+
+        if loadingURL {
+            decisionHandler(.cancel)
+            return
+        }
+
+        loadingURL = true
+
+        if allowedURLs.contains(url.absoluteString) {
+            loadingURL = false
+            decisionHandler(.allow)
+            return
+        }
+        
+        validateSSLCertificate(url: url) { [weak self] isValid in
+            guard let self = self else {
+                decisionHandler(.cancel)
+                return
+            }
+
+            DispatchQueue.main.async {
+                if isValid {
+                    self.allowedURLs.insert(url.absoluteString)
+                    self.loadingURL = false
+                    decisionHandler(.allow)
+                } else {
+                    let host = url.host ?? ""
+                    var messageText = "You're about to access a website that is not currently trusted by your Nexilis Browser. This website's security certificate is not recognized.\n\nDo you wish to proceed to <<domain>> and trust the website's security certificate?\n\nNote: Adding a website to the trusted list may increase your risk of security vulnerability".localized()
+                    messageText = messageText.replacingOccurrences(of: "<<domain>>", with: host)
+
+                    let alert = UIAlertController(title: "Warning Unknown Url!".localized(),
+                                                  message: messageText,
+                                                  preferredStyle: .alert)
+
+                    alert.addAction(UIAlertAction(title: "Yes", style: .default) { _ in
+                        let storedCertificate = Utils.getCertificatePinningWebview()
+                        if let jsonData = storedCertificate.data(using: .utf8),
+                           let certJson = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: String] {
+                            var certJson = certJson
+                            certJson[host] = self.blockedCertificate
+                            if let jsonData = try? JSONSerialization.data(withJSONObject: certJson, options: []),
+                               let jsonString = String(data: jsonData, encoding: .utf8) {
+                                Utils.setCertificatePinningWebview(value: jsonString)
+                            }
+                        }
+
+                        self.allowedURLs.insert(url.absoluteString)
+                        self.loadingURL = false
+                        decisionHandler(.allow)
+                    })
+
+                    alert.addAction(UIAlertAction(title: "No", style: .cancel) { _ in
+                        self.loadingURL = false
+                        decisionHandler(.cancel)
+                    })
+
+                    if self.presentedViewController == nil {
+                        self.present(alert, animated: true, completion: nil)
+                    } else {
+                        self.loadingURL = false
+                        decisionHandler(.cancel)
+                    }
+                }
+            }
+        }
+    }
+    
+    private func validateSSLCertificate(url: URL, completion: @escaping (Bool) -> Void) {
+        let session = URLSession(configuration: .ephemeral, delegate: self, delegateQueue: nil)
+        let request = URLRequest(url: url)
+
+        let task = session.dataTask(with: request) { _, response, error in
+            if let error = error {
+                completion(false)
+                return
+            }
+            completion(true)
+        }
+        task.resume()
+    }
+}
+
+extension StatusUpdateVC: URLSessionDelegate {
+    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
+        guard let serverTrust = challenge.protectionSpace.serverTrust else {
+            completionHandler(.cancelAuthenticationChallenge, nil)
+            return
+        }
+        if let publicKeyHash = extractPublicKeyHash(from: serverTrust) {
+            let domain = challenge.protectionSpace.host
+            let storedCertificate = Utils.getCertificatePinningWebview()
+            if let jsonData = storedCertificate.data(using: .utf8),
+               let certJson = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: String] {
+                if publicKeyHash == certJson[domain] {
+                    completionHandler(.useCredential, URLCredential(trust: serverTrust))
+                } else {
+                    blockedCertificate = publicKeyHash
+                    completionHandler(.cancelAuthenticationChallenge, nil)
+                }
+            }
+        } else {
+            completionHandler(.cancelAuthenticationChallenge, nil)
+        }
+    }
+    
+    func extractPublicKeyHash(from serverTrust: SecTrust) -> String? {
+        guard let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else { return nil }
+        guard let publicKey = SecCertificateCopyKey(certificate) else { return nil }
+        
+        var error: Unmanaged<CFError>?
+        guard let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, &error) as Data? else {
+            return nil
+        }
+        
+        // Compute SHA-256 hash
+        var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
+        publicKeyData.withUnsafeBytes {
+            _ = CC_SHA256($0.baseAddress, CC_LONG(publicKeyData.count), &hash)
+        }
+        
+        let hashData = Data(hash)
+        let base64Hash = hashData.base64EncodedString()
+        
+        return base64Hash
+    }
+}

+ 6 - 0
AppBuilder/AppBuilder/ViewController.swift

@@ -38,6 +38,7 @@ class ViewController: UITabBarController, UITabBarControllerDelegate, SettingMAB
     var chatWATab : UINavigationController?
     var communityTab : UINavigationController?
     var secureFolderTab : UINavigationController?
+    var statusUpdateTab : UINavigationController?
     let emptyTab = EmptyTabViewController()
     public static var isTab1 = true
     public static var isTab2 = false
@@ -101,6 +102,7 @@ class ViewController: UITabBarController, UITabBarControllerDelegate, SettingMAB
         chatWATab = UINavigationController(rootViewController: ChatWALikeVC())
         communityTab = UINavigationController(rootViewController: CommunityList())
         secureFolderTab = UINavigationController(rootViewController: SecureFolderViewController())
+        statusUpdateTab = UINavigationController(rootViewController: StatusUpdateVC())
         
         self.delegate = self
         
@@ -112,6 +114,8 @@ class ViewController: UITabBarController, UITabBarControllerDelegate, SettingMAB
         chatWATab?.tabBarItem = UITabBarItem(title: "", image: resizeImage(image: self.traitCollection.userInterfaceStyle == .dark ? UIImage(named: "tab_2_icon")!.withTintColor(.white) : UIImage(named: "tab_2_icon")!, targetSize: CGSize(width: 25, height: 25)).withRenderingMode(.alwaysOriginal), selectedImage: resizeImage(image: UIImage(named: "tab_2_icon")!, targetSize: CGSize(width: 25, height: 25)).withRenderingMode(.alwaysOriginal).withTintColor(self.traitCollection.userInterfaceStyle == .dark ? .lightGray : .mainColor))
         communityTab?.tabBarItem = UITabBarItem(title: "", image: resizeImage(image: self.traitCollection.userInterfaceStyle == .dark ? UIImage(named: "tab_2_icon")!.withTintColor(.white) : UIImage(named: "tab_2_icon")!, targetSize: CGSize(width: 25, height: 25)).withRenderingMode(.alwaysOriginal), selectedImage: resizeImage(image: UIImage(named: "tab_2_icon")!, targetSize: CGSize(width: 25, height: 25)).withRenderingMode(.alwaysOriginal).withTintColor(self.traitCollection.userInterfaceStyle == .dark ? .lightGray : .mainColor))
         secureFolderTab?.tabBarItem = UITabBarItem(title: "", image: resizeImage(image: self.traitCollection.userInterfaceStyle == .dark ? UIImage(named: "tab_2_icon")!.withTintColor(.white) : UIImage(named: "tab_2_icon")!, targetSize: CGSize(width: 25, height: 25)).withRenderingMode(.alwaysOriginal), selectedImage: resizeImage(image: UIImage(named: "tab_2_icon")!, targetSize: CGSize(width: 25, height: 25)).withRenderingMode(.alwaysOriginal).withTintColor(self.traitCollection.userInterfaceStyle == .dark ? .lightGray : .mainColor))
+        statusUpdateTab?.tabBarItem = UITabBarItem(title: "", image: resizeImage(image: self.traitCollection.userInterfaceStyle == .dark ? UIImage(named: "tab_1_icon")!.withTintColor(.white) : UIImage(named: "tab_1_icon")!, targetSize: CGSize(width: 25, height: 25)).withRenderingMode(.alwaysOriginal), selectedImage: resizeImage(image: UIImage(named: "tab_1_icon")!, targetSize: CGSize(width: 25, height: 25)).withRenderingMode(.alwaysOriginal).withTintColor(self.traitCollection.userInterfaceStyle == .dark ? .lightGray : .mainColor))
+        
         var i = 0
         var j = 0
         while j < customTab.count {
@@ -135,6 +139,8 @@ class ViewController: UITabBarController, UITabBarControllerDelegate, SettingMAB
                     tabs.append(callTab!)
                 case "16":
                     tabs.append(communityTab!)
+                case "17":
+                    tabs.append(statusUpdateTab!)
                 case "18":
                     tabs.append(chatWATab!)
                 default:

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

@@ -438,6 +438,9 @@ public class Database {
                                 "'archived' integer NOT NULL default 0," +
                                 "PRIMARY KEY ('l_pin'))", values: nil)
         
+        try fmdb.executeUpdate("CREATE UNIQUE INDEX IF NOT EXISTS index_MESSAGE_SUMMARY_UK1 on MESSAGE_SUMMARY (l_pin)", values: nil)
+        try fmdb.executeUpdate("CREATE INDEX IF NOT EXISTS index_MESSAGE_SUMMARY_UK2 on MESSAGE_SUMMARY (message_id)", values: nil)
+        
         try fmdb.executeUpdate("CREATE TABLE IF NOT EXISTS 'OUTGOING' (" +
                                 "'id' text PRIMARY KEY NOT NULL," +
                                 "'package' text," +

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

@@ -19,7 +19,7 @@ import CryptoKit
 import WebKit
 
 public class Nexilis: NSObject {
-    public static var cpaasVersion = "5.0.50"
+    public static var cpaasVersion = "5.0.51"
     public static var sAPIKey = ""
     
     public static var ADDRESS = ""

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

@@ -109,6 +109,10 @@ public final class Utils {
         SecureUserDefaults.shared.set(value, forKey: "app_builder_url_third_tab")
     }
     
+    static func setURLStatusUpdate(value: String) {
+        SecureUserDefaults.shared.set(value, forKey: "app_builder_url_status_update")
+    }
+    
     static func setURLBase(value: String) {
         SecureUserDefaults.shared.set(value, forKey: "app_builder_url_base")
     }
@@ -815,6 +819,9 @@ public final class Utils {
                     if Array(json.keys)[i] == "app_builder_url_third_tab" {
                         Utils.setURLThirdTab(value: Array(json.values)[i] as? String ?? "")
                     }
+                    if Array(json.keys)[i] == "app_builder_url_status_update" {
+                        Utils.setURLStatusUpdate(value: Array(json.values)[i] as? String ?? "")
+                    }
                     if Array(json.keys)[i] == "app_builder_custom_tab" {
                         Utils.setCustomTab(cust: Array(json.values)[i] as? String ?? "")
                     }

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

@@ -437,6 +437,7 @@ public class BNIBookingWebView: UIViewController, WKNavigationDelegate, UIScroll
             Utils.setMyTheme(value: param1)
             Utils.setIsLoadThemeFromOther(value: true)
             Utils.resetValueSuperApp()
+            Utils.setLastTabSelected(value: 0)
             if let jsonArray = try! JSONSerialization.jsonObject(with: param1.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [AnyObject] {
                 do {
                     for json in jsonArray {
@@ -458,6 +459,9 @@ public class BNIBookingWebView: UIViewController, WKNavigationDelegate, UIScroll
                         if json["KEY"]  as! String == "app_builder_url_webview_6" {
                             Utils.setURLWv6(value: json["VALUE"] as! String)
                         }
+                        if json["KEY"]  as! String == "app_builder_url_status_update" {
+                            Utils.setURLStatusUpdate(value: json["VALUE"] as! String)
+                        }
                         if json["KEY"]  as! String == "app_builder_custom_tab" {
                             Utils.setCustomTab(cust: json["VALUE"] as! String)
                         }

+ 2 - 0
NexilisLite/NexilisLite/Source/View/Call/CallLogVC.swift

@@ -22,6 +22,7 @@ public class CallLogVC: UIViewController, UITableViewDataSource, UITableViewDele
         tableView.tableFooterView = UIView()
         tableView.sectionHeaderHeight = 0
         tableView.sectionFooterHeight = 0
+        tableView.backgroundColor = .clear
         tableView.automaticallyAdjustsScrollIndicatorInsets = false
         if #available(iOS 15.0, *) {
             tableView.sectionHeaderTopPadding = 0
@@ -248,6 +249,7 @@ public class CallLogVC: UIViewController, UITableViewDataSource, UITableViewDele
     public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
         let call = calls[indexPath.row]
         let cell = tableView.dequeueReusableCell(withIdentifier: "cellCallLog", for: indexPath)
+        cell.backgroundColor = .clear
         let textTime = UILabel()
         textTime.text = call.time
         textTime.font = .systemFont(ofSize: 14)

+ 4 - 4
NexilisLite/NexilisLite/Source/View/Chat/ChatWALikeVC.swift

@@ -56,6 +56,7 @@ public class ChatWALikeVC: UIViewController, UITableViewDataSource, UITableViewD
         tableView.tableFooterView = UIView()
         tableView.sectionHeaderHeight = 0
         tableView.sectionFooterHeight = 0
+        tableView.backgroundColor = .clear
         tableView.automaticallyAdjustsScrollIndicatorInsets = false
         if #available(iOS 15.0, *) {
             tableView.sectionHeaderTopPadding = 0
@@ -127,10 +128,7 @@ public class ChatWALikeVC: UIViewController, UITableViewDataSource, UITableViewD
     private func refresh() {
         getData { [self] in
             if tempChats.count == 0 {
-                navigationItem.searchController = nil
-                DispatchQueue.main.async {
-                    self.navigationController?.navigationBar.sizeToFit()
-                }
+                searchController.searchBar.isHidden = true
                 if textChatEmpty.isDescendant(of: view) {
                     textChatEmpty.removeFromSuperview()
                 }
@@ -151,6 +149,7 @@ public class ChatWALikeVC: UIViewController, UITableViewDataSource, UITableViewD
                 if textChatEmpty.isDescendant(of: view) {
                     textChatEmpty.removeFromSuperview()
                 }
+                searchController.searchBar.isHidden = false
             }
         }
     }
@@ -352,6 +351,7 @@ public class ChatWALikeVC: UIViewController, UITableViewDataSource, UITableViewD
     
     public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
         let cell = tableView.dequeueReusableCell(withIdentifier: "cellChatWA", for: indexPath)
+        cell.backgroundColor = .clear
         let content = cell.contentView
         if content.subviews.count > 0 {
             content.subviews.forEach { $0.removeFromSuperview() }

+ 1 - 0
NexilisLite/NexilisLite/Source/View/Chat/CommunityList.swift

@@ -21,6 +21,7 @@ public class CommunityList: UIViewController, UITableViewDataSource, UITableView
         tableView.tableFooterView = UIView()
         tableView.sectionHeaderHeight = 0
         tableView.sectionFooterHeight = 0
+        tableView.backgroundColor = .clear
         tableView.automaticallyAdjustsScrollIndicatorInsets = false
         if #available(iOS 15.0, *) {
             tableView.sectionHeaderTopPadding = 0

+ 11 - 12
NexilisLite/NexilisLite/Source/View/Chat/EditorGroup.swift

@@ -3948,15 +3948,15 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
                 row[TypeDataMessage.is_pinned] = cursorData.string(forColumnIndex: 27)
                 row["progress"] = 0.0
                 row["isSelected"] = false
-                if !self.dataDates.contains("Today".localized()) {
-                    self.dataDates.append("Today".localized())
-                    self.tableChatView.insertSections(IndexSet(integer: self.dataDates.count - 1), with: .none)
-                }
                 row["chat_date"] = "Today".localized()
                 cursorData.close()
             }
         })
         DispatchQueue.main.async {
+            if !self.dataDates.contains("Today".localized()) {
+                self.dataDates.append("Today".localized())
+                self.tableChatView.insertSections(IndexSet(integer: self.dataDates.count - 1), with: .none)
+            }
             self.tableChatView.beginUpdates()
             self.dataMessages.append(row)
             self.tableChatView.insertRows(at: [IndexPath(row: self.dataMessages.filter({ $0["chat_date"]  as? String ?? "" == self.dataDates[self.dataDates.count - 1]}).count - 1, section: self.dataDates.count - 1)], with: .none)
@@ -4848,16 +4848,15 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
         containerView.addSubview(dateView)
         dateView.translatesAutoresizingMaskIntoConstraints = false
         var topAnchor = dateView.topAnchor.constraint(equalTo: containerView.topAnchor)
-        topAnchor = dateView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 10.0)
+        topAnchor = dateView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 10)
         NSLayoutConstraint.activate([
             topAnchor,
-            dateView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -10.0),
+            dateView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
             dateView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
-//            dateView.heightAnchor.constraint(equalToConstant: 30),
             dateView.widthAnchor.constraint(greaterThanOrEqualToConstant: 60)
         ])
         dateView.backgroundColor = .orangeColor
-        dateView.layer.cornerRadius = 15.0
+        dateView.layer.cornerRadius = 8.0
         dateView.clipsToBounds = true
         
         let labelDate = UILabel()
@@ -4880,7 +4879,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
         if tableView == tableMention || tableView == tableMentionEdit || tableView == tableViewConfigFile {
             return 0
         }
-        return 50
+        return 30
     }
     
     public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
@@ -5115,13 +5114,13 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
         if messageIdChat.contains("NTFPIN_") {
             containerMessage.backgroundColor = .orangeColor
             containerMessage.anchor(top: cellMessage.contentView.topAnchor, bottom: cellMessage.contentView.bottomAnchor, paddingTop: 5, paddingBottom: 5, centerX: cellMessage.contentView.centerXAnchor, minWidth: 40, maxWidth: UIScreen.main.bounds.width - 40)
-            containerMessage.layer.cornerRadius = 15
+            containerMessage.layer.cornerRadius = 8
             containerMessage.clipsToBounds = true
             
             let textMessage = UILabel()
             containerMessage.addSubview(textMessage)
             textMessage.textAlignment = .center
-            textMessage.anchor(top: containerMessage.topAnchor, left: containerMessage.leftAnchor, bottom: containerMessage.bottomAnchor, right: containerMessage.rightAnchor, paddingTop: 10, paddingLeft: 10, paddingBottom: 10, paddingRight: 10)
+            textMessage.anchor(top: containerMessage.topAnchor, left: containerMessage.leftAnchor, bottom: containerMessage.bottomAnchor, right: containerMessage.rightAnchor, paddingTop: 5, paddingLeft: 10, paddingBottom: 5, paddingRight: 10)
             textMessage.font = .systemFont(ofSize: 14)
             textMessage.text = dataMessages[indexPath.row][TypeDataMessage.message_text]  as? String ?? ""
             textMessage.textColor = .white
@@ -6668,7 +6667,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
             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 && topMarginText.constant == 15.0 {
+                if data.count != 0 && (topMarginText.constant == 32.0 || topMarginText.constant == 100.0) {
                     addTopMargin = false
                 }
             }

+ 11 - 12
NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift

@@ -2007,15 +2007,15 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                 row[TypeDataMessage.is_pinned] = cursorData.string(forColumnIndex: 27)
                 row["progress"] = 0.0
                 row["isSelected"] = false
-                if !self.dataDates.contains("Today".localized()) {
-                    self.dataDates.append("Today".localized())
-                    self.tableChatView.insertSections(IndexSet(integer: self.dataDates.count - 1), with: .none)
-                }
                 row["chat_date"] = "Today".localized()
                 cursorData.close()
             }
         })
         DispatchQueue.main.async {
+            if !self.dataDates.contains("Today".localized()) {
+                self.dataDates.append("Today".localized())
+                self.tableChatView.insertSections(IndexSet(integer: self.dataDates.count - 1), with: .none)
+            }
             self.tableChatView.beginUpdates()
             self.dataMessages.append(row)
             self.tableChatView.insertRows(at: [IndexPath(row: self.dataMessages.filter({ $0["chat_date"]  as? String ?? "" == self.dataDates[self.dataDates.count - 1]}).count - 1, section: self.dataDates.count - 1)], with: .none)
@@ -5976,16 +5976,15 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
         containerView.addSubview(dateView)
         dateView.translatesAutoresizingMaskIntoConstraints = false
         var topAnchor = dateView.topAnchor.constraint(equalTo: containerView.topAnchor)
-        topAnchor = dateView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 10.0)
+        topAnchor = dateView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 10)
         NSLayoutConstraint.activate([
             topAnchor,
-            dateView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -10.0),
+            dateView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
             dateView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
-//            dateView.heightAnchor.constraint(equalToConstant: 30),
             dateView.widthAnchor.constraint(greaterThanOrEqualToConstant: 60)
         ])
         dateView.backgroundColor = .orangeColor
-        dateView.layer.cornerRadius = 15.0
+        dateView.layer.cornerRadius = 8.0
         dateView.clipsToBounds = true
         
         let labelDate = UILabel()
@@ -6014,7 +6013,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
         if tableView == tableViewConfigFile {
             return 0
         }
-        return 50
+        return 30
     }
     
     public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
@@ -6296,13 +6295,13 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
         if messageIdChat.contains("NTFPIN_") {
             containerMessage.backgroundColor = .orangeColor
             containerMessage.anchor(top: cell.contentView.topAnchor, bottom: cell.contentView.bottomAnchor, paddingTop: 5, paddingBottom: 5, centerX: cell.contentView.centerXAnchor, minWidth: 40, maxWidth: UIScreen.main.bounds.width - 40)
-            containerMessage.layer.cornerRadius = 15
+            containerMessage.layer.cornerRadius = 8
             containerMessage.clipsToBounds = true
             
             let textMessage = UILabel()
             containerMessage.addSubview(textMessage)
             textMessage.textAlignment = .center
-            textMessage.anchor(top: containerMessage.topAnchor, left: containerMessage.leftAnchor, bottom: containerMessage.bottomAnchor, right: containerMessage.rightAnchor, paddingTop: 10, paddingLeft: 10, paddingBottom: 10, paddingRight: 10)
+            textMessage.anchor(top: containerMessage.topAnchor, left: containerMessage.leftAnchor, bottom: containerMessage.bottomAnchor, right: containerMessage.rightAnchor, paddingTop: 5, paddingLeft: 10, paddingBottom: 5, paddingRight: 10)
             textMessage.font = .systemFont(ofSize: 14)
             textMessage.text = dataMessages[indexPath.row][TypeDataMessage.message_text]  as? String ?? ""
             textMessage.textColor = .white
@@ -7959,7 +7958,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
             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 && topMarginText.constant == 15.0 {
+                if data.count != 0 && (topMarginText.constant == 15.0 || topMarginText.constant == 100.0) {
                     addTopMargin = false
                 }
             }

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

@@ -560,6 +560,7 @@ public class SignUpSignIn: UIViewController {
                                 self.navigationController?.popViewController(animated: true)
                             }
                         }
+                        Nexilis.getFeatureAccess()
                     })
                 })
             }