Эх сурвалжийг харах

update for release 5.0.63

alqindiirsyam 2 долоо хоног өмнө
parent
commit
88beff4f6b

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

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

+ 1 - 1
AppBuilder/AppBuilder/AppDelegate.swift

@@ -22,7 +22,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, PKPushRegistryDelegate {
             showButton = true
             showButton = true
         }
         }
         Nexilis.isShowForceSignIn = false
         Nexilis.isShowForceSignIn = false
-        APIS.connect(appName: appName , apiKey: apikey, delegate: self, showButton: showButton, fromMAB: true) //23091CF494A11149F5A8FC8D17FF690DC69AE656F91B86070A11506ED24144F5(BPKH) //38747683290F62E9667A018F490396EAE47BC16ADECD85B7E865C733E6DBD6A2(OneApp)
+        APIS.connect(appName: appName , apiKey: apikey, delegate: self, showButton: showButton, fromMAB: true)
         registerForPushNotifications()
         registerForPushNotifications()
         registerForVoIPPushNotifications()
         registerForVoIPPushNotifications()
         FirebaseApp.configure()
         FirebaseApp.configure()

+ 5 - 1
AppBuilder/AppBuilder/FourthTabViewController.swift

@@ -275,7 +275,11 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
             Item.menus["Config"]?.append(Item(icon: UIImage(systemName: "paintbrush"), title: "Switch Style".localized()))
             Item.menus["Config"]?.append(Item(icon: UIImage(systemName: "paintbrush"), title: "Switch Style".localized()))
         }
         }
         if Utils.getIsLoadThemeFromOther() {
         if Utils.getIsLoadThemeFromOther() {
-            Item.menus["Config"]?.insert(Item(icon: UIImage(systemName: "iphone"), title: "Back to Company App".localized()), at: 1)
+            if Item.menus["Config"]!.count > 0 {
+                Item.menus["Config"]?.insert(Item(icon: UIImage(systemName: "iphone"), title: "Back to Company App".localized()), at: 1)
+            } else {
+                Item.menus["Config"]?.append(Item(icon: UIImage(systemName: "iphone"), title: "Back to Company App".localized()))
+            }
         }
         }
         
         
         Item.menus["Call"] = [
         Item.menus["Call"] = [

+ 8 - 0
NexilisLite/NexilisLite.xcodeproj/project.pbxproj

@@ -226,6 +226,8 @@
 		CD63125F2DA925960088964E /* CallModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD63125C2DA925960088964E /* CallModel.swift */; };
 		CD63125F2DA925960088964E /* CallModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD63125C2DA925960088964E /* CallModel.swift */; };
 		CD637E3D2D9534ED004780FB /* NexilisLite.podspec in Resources */ = {isa = PBXBuildFile; fileRef = CD46A0BE2A0CE4FD009E4C87 /* NexilisLite.podspec */; };
 		CD637E3D2D9534ED004780FB /* NexilisLite.podspec in Resources */ = {isa = PBXBuildFile; fileRef = CD46A0BE2A0CE4FD009E4C87 /* NexilisLite.podspec */; };
 		CD7054EA2AD39DE2003741BF /* ChatGPTBotView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD7054E92AD39DE2003741BF /* ChatGPTBotView.swift */; };
 		CD7054EA2AD39DE2003741BF /* ChatGPTBotView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD7054E92AD39DE2003741BF /* ChatGPTBotView.swift */; };
+		CD71B2252E72A3E100D693EA /* Form.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD71B2232E72A3E100D693EA /* Form.swift */; };
+		CD71B2262E72A3E100D693EA /* FormItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD71B2242E72A3E100D693EA /* FormItem.swift */; };
 		CD9551592A664BDA00AF6476 /* Poppins-LightItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = CD9551402A664BD400AF6476 /* Poppins-LightItalic.otf */; };
 		CD9551592A664BDA00AF6476 /* Poppins-LightItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = CD9551402A664BD400AF6476 /* Poppins-LightItalic.otf */; };
 		CD95515A2A664BDA00AF6476 /* Poppins-MediumItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = CD95513F2A664BD400AF6476 /* Poppins-MediumItalic.otf */; };
 		CD95515A2A664BDA00AF6476 /* Poppins-MediumItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = CD95513F2A664BD400AF6476 /* Poppins-MediumItalic.otf */; };
 		CD95515B2A664BDA00AF6476 /* Poppins-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = CD9551412A664BD400AF6476 /* Poppins-Bold.otf */; };
 		CD95515B2A664BDA00AF6476 /* Poppins-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = CD9551412A664BD400AF6476 /* Poppins-Bold.otf */; };
@@ -518,6 +520,8 @@
 		CD63125C2DA925960088964E /* CallModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallModel.swift; sourceTree = "<group>"; };
 		CD63125C2DA925960088964E /* CallModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallModel.swift; sourceTree = "<group>"; };
 		CD63125D2DA925960088964E /* Group_NM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Group_NM.swift; sourceTree = "<group>"; };
 		CD63125D2DA925960088964E /* Group_NM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Group_NM.swift; sourceTree = "<group>"; };
 		CD7054E92AD39DE2003741BF /* ChatGPTBotView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatGPTBotView.swift; sourceTree = "<group>"; };
 		CD7054E92AD39DE2003741BF /* ChatGPTBotView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatGPTBotView.swift; sourceTree = "<group>"; };
+		CD71B2232E72A3E100D693EA /* Form.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Form.swift; sourceTree = "<group>"; };
+		CD71B2242E72A3E100D693EA /* FormItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormItem.swift; sourceTree = "<group>"; };
 		CD9551332A664BD400AF6476 /* Poppins-SemiBoldItalic.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Poppins-SemiBoldItalic.otf"; sourceTree = "<group>"; };
 		CD9551332A664BD400AF6476 /* Poppins-SemiBoldItalic.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Poppins-SemiBoldItalic.otf"; sourceTree = "<group>"; };
 		CD9551342A664BD400AF6476 /* Poppins-ExtraBoldItalic.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Poppins-ExtraBoldItalic.otf"; sourceTree = "<group>"; };
 		CD9551342A664BD400AF6476 /* Poppins-ExtraBoldItalic.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Poppins-ExtraBoldItalic.otf"; sourceTree = "<group>"; };
 		CD9551352A664BD400AF6476 /* SIL Open Font License.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "SIL Open Font License.txt"; sourceTree = "<group>"; };
 		CD9551352A664BD400AF6476 /* SIL Open Font License.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "SIL Open Font License.txt"; sourceTree = "<group>"; };
@@ -693,6 +697,8 @@
 		CD1E715E2A0BA86100BF871F /* Model */ = {
 		CD1E715E2A0BA86100BF871F /* Model */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				CD71B2232E72A3E100D693EA /* Form.swift */,
+				CD71B2242E72A3E100D693EA /* FormItem.swift */,
 				CD21CA5A2E0AA0C600DA6B77 /* CommunityModel.swift */,
 				CD21CA5A2E0AA0C600DA6B77 /* CommunityModel.swift */,
 				CD63125C2DA925960088964E /* CallModel.swift */,
 				CD63125C2DA925960088964E /* CallModel.swift */,
 				CD1E71622A0BA86100BF871F /* CategoryCC.swift */,
 				CD1E71622A0BA86100BF871F /* CategoryCC.swift */,
@@ -1501,6 +1507,8 @@
 				CD1E72162A0BA86100BF871F /* User.swift in Sources */,
 				CD1E72162A0BA86100BF871F /* User.swift in Sources */,
 				CD1E720A2A0BA86100BF871F /* APIS.swift in Sources */,
 				CD1E720A2A0BA86100BF871F /* APIS.swift in Sources */,
 				CD1E72062A0BA86100BF871F /* OutgoingThread.swift in Sources */,
 				CD1E72062A0BA86100BF871F /* OutgoingThread.swift in Sources */,
+				CD71B2252E72A3E100D693EA /* Form.swift in Sources */,
+				CD71B2262E72A3E100D693EA /* FormItem.swift in Sources */,
 				CD1E722F2A0BA86100BF871F /* ProfileView.swift in Sources */,
 				CD1E722F2A0BA86100BF871F /* ProfileView.swift in Sources */,
 				CD1E720D2A0BA86100BF871F /* InquiryThread.swift in Sources */,
 				CD1E720D2A0BA86100BF871F /* InquiryThread.swift in Sources */,
 			);
 			);

+ 75 - 70
NexilisLite/NexilisLite/Source/APIS.swift

@@ -1403,7 +1403,17 @@ public class APIS: NSObject {
         }
         }
         let idx = Nexilis.IDX_PPOB
         let idx = Nexilis.IDX_PPOB
         let url = getURLFB(idx: idx)
         let url = getURLFB(idx: idx)
-        print("HUHU: \(idx) <><> \(url)")
+        Nexilis.buttonClicked(index: idx, id: url)
+    }
+    
+    public static func openWallet() {
+        let isChangeProfile = Utils.getSetProfile()
+        if !isChangeProfile {
+            APIS.showChangeProfile()
+            return
+        }
+        let idx = Nexilis.IDX_WALLET
+        let url = getURLFB(idx: idx)
         Nexilis.buttonClicked(index: idx, id: url)
         Nexilis.buttonClicked(index: idx, id: url)
     }
     }
     
     
@@ -1415,7 +1425,6 @@ public class APIS: NSObject {
         }
         }
         let idx = Nexilis.IDX_SOCIAL_COMMERCE
         let idx = Nexilis.IDX_SOCIAL_COMMERCE
         let url = getURLFB(idx: idx)
         let url = getURLFB(idx: idx)
-        print("HUHU: \(idx) <><> \(url)")
         Nexilis.buttonClicked(index: idx, id: url)
         Nexilis.buttonClicked(index: idx, id: url)
     }
     }
     
     
@@ -1529,22 +1538,21 @@ public class APIS: NSObject {
     }
     }
     
     
     public static func sendPushToken(_ token: String, isResend: Bool = false, isCall: Bool = false) {
     public static func sendPushToken(_ token: String, isResend: Bool = false, isCall: Bool = false) {
-        DispatchQueue.global().async{
-            if !isCall {
-                if Utils.getTokenAPN().isEmpty || token != Utils.getTokenAPN() {
-                    Utils.setTokenAPN(value: token)
-                }
-                DispatchQueue.global().async {
-                    while API.nGetCLXConnState() == 0 {
-                        Thread.sleep(forTimeInterval: 1)
-                    }
-                    _ = Nexilis.write(message: CoreMessage_TMessageBank.getToken(token: token, tokenCall: Utils.getTokenCall()))
-                }
+        if !isCall {
+            if Utils.getTokenAPN().isEmpty || token != Utils.getTokenAPN() {
+                Utils.setTokenAPN(value: token)
             }
             }
-            else {
-                if Utils.getTokenCall().isEmpty || token != Utils.getTokenCall() {
-                    Utils.setTokenCall(value: token)
+            DispatchQueue.global().async {
+                while API.nGetCLXConnState() == 0 {
+                    Thread.sleep(forTimeInterval: 1)
                 }
                 }
+                _ = Nexilis.write(message: CoreMessage_TMessageBank.getToken(token: token, tokenCall: Utils.getTokenCall()))
+            }
+        }
+        else {
+            if Utils.getTokenCall().isEmpty || token != Utils.getTokenCall() {
+                Utils.setTokenCall(value: token)
+            }
 //                DispatchQueue.global().async {
 //                DispatchQueue.global().async {
 //                    while API.nGetCLXConnState() == 0 {
 //                    while API.nGetCLXConnState() == 0 {
 //                        Thread.sleep(forTimeInterval: 1)
 //                        Thread.sleep(forTimeInterval: 1)
@@ -1552,7 +1560,6 @@ public class APIS: NSObject {
 //                    print("SEND TOKEN CALL")
 //                    print("SEND TOKEN CALL")
 //                    _ = Nexilis.write(message: CoreMessage_TMessageBank.getToken(token: token, isCall: true))
 //                    _ = Nexilis.write(message: CoreMessage_TMessageBank.getToken(token: token, isCall: true))
 //                }
 //                }
-            }
         }
         }
     }
     }
     
     
@@ -1561,7 +1568,7 @@ public class APIS: NSObject {
     public static func showNotificationNexilis(_ userInfo: [AnyHashable : Any], _ completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
     public static func showNotificationNexilis(_ userInfo: [AnyHashable : Any], _ completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
         DispatchQueue.main.async {
         DispatchQueue.main.async {
             if checkAppStateisBackground() {
             if checkAppStateisBackground() {
-                DispatchQueue.global().async {
+                DispatchQueue.global(qos: .userInitiated).async {
                     if let payload = userInfo["payload"] as? [String: Any] {
                     if let payload = userInfo["payload"] as? [String: Any] {
                         if let messagePayload = payload["message"] as? [String: Any] {
                         if let messagePayload = payload["message"] as? [String: Any] {
                             if let data = messagePayload["data"] as? [String: Any] {
                             if let data = messagePayload["data"] as? [String: Any] {
@@ -1727,64 +1734,55 @@ public class APIS: NSObject {
     }
     }
     
     
     private static func getMessageById(id: String, retry: Int = 0, completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
     private static func getMessageById(id: String, retry: Int = 0, completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
-        DispatchQueue.global().async {
-            let parameter: [String : Any] = [
-                "pin": User.getMyPin() ?? "",
-                "message_id": id
-            ]
-            Utils.postDataWithCookiesAndUserAgent(from: URL(string: Utils.getDomainOpr() + "pull_notification")!, parameter: parameter, isFormData: true) { data, response, error in
-                if error != nil {
-                    let ret = retry + 1
-                    if ret <= 5 {
-                        getMessageById(id: id, retry: ret, completionHandler: completionHandler)
-                    } else {
-                        completionHandler(.failed)
-                    }
-                } else if let data = data {
-                    do {
-                        if let dataString = String(data: data, encoding: .utf8) {
-                            if let jsonObj = try JSONSerialization.jsonObject(with: dataString.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [String: Any] {
-                                let dataObj = jsonObj["data"] as? String ?? ""
-                                let message = TMessage(data: dataObj)
-                                if Utils.getSecureFolderOffline() == "0" && IncomingThread.dispatch == nil {
-                                    if API.nGetCLXConnState() == 0 {
-                                        do {
-                                            let id = Utils.getConnectionID()
-                                            try API.initConnection(sAPIK: Nexilis.sAPIKey, cbiI: Callback(), sTCPAddr: Nexilis.ADDRESS, nTCPPort: Nexilis.PORT, sUserID: id, sStartWH: "09:00")
-                                        } catch {}
-                                    }
-                                    if FileEncryption.shared.aesKey == nil {
-                                        IncomingThread.dispatch = DispatchGroup()
-                                        IncomingThread.dispatch?.enter()
-                                        Nexilis.getFeatureAccess()
-                                        IncomingThread.dispatch?.wait()
-                                        IncomingThread.dispatch = nil
-                                    }
-                                }
-//                                print("save from APIS")
-                                Nexilis.saveMessage(message: message, withStatus: false, fromAPNS: true)
-                                ackAPN(id: id)
-                                DispatchQueue.main.async {
-                                    UIApplication.shared.applicationIconBadgeNumber = Int(APIS.getTotalCounter())
+        let parameter: [String : Any] = [
+            "pin": User.getMyPin() ?? "",
+            "message_id": id
+        ]
+        Utils.postDataWithCookiesAndUserAgent(from: URL(string: Utils.getDomainOpr() + "pull_notification")!, parameter: parameter, isFormData: true) { data, response, error in
+            if error != nil {
+                let ret = retry + 1
+                if ret <= 5 {
+                    getMessageById(id: id, retry: ret, completionHandler: completionHandler)
+                } else {
+                    completionHandler(.failed)
+                }
+            } else if let data = data {
+                do {
+                    if let dataString = String(data: data, encoding: .utf8) {
+                        if let jsonObj = try JSONSerialization.jsonObject(with: dataString.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [String: Any] {
+                            let dataObj = jsonObj["data"] as? String ?? ""
+                            let message = TMessage(data: dataObj)
+                            if Utils.getSecureFolderOffline() == "0" && IncomingThread.dispatch == nil {
+                                if API.nGetCLXConnState() == 0 {
+                                    do {
+                                        let id = Utils.getConnectionID()
+                                        try API.initConnection(sAPIK: Nexilis.sAPIKey, cbiI: Callback(), sTCPAddr: Nexilis.ADDRESS, nTCPPort: Nexilis.PORT, sUserID: id, sStartWH: "09:00")
+                                    } catch {}
                                 }
                                 }
-                            } else {
-                                let ret = retry + 1
-                                if ret <= 5 {
-                                    getMessageById(id: id, retry: ret, completionHandler: completionHandler)
-                                } else {
-                                    completionHandler(.failed)
+                                if FileEncryption.shared.aesKey == nil {
+                                    IncomingThread.dispatch = DispatchGroup()
+                                    IncomingThread.dispatch?.enter()
+                                    Nexilis.getFeatureAccess()
+                                    IncomingThread.dispatch?.wait()
+                                    IncomingThread.dispatch = nil
                                 }
                                 }
                             }
                             }
-                        }
-                    } catch {
-                        let ret = retry + 1
-                        if ret <= 5 {
-                            getMessageById(id: id, retry: ret, completionHandler: completionHandler)
+//                                print("save from APIS")
+                            Nexilis.saveMessage(message: message, withStatus: false, fromAPNS: true)
+                            ackAPN(id: id)
+                            DispatchQueue.main.async {
+                                UIApplication.shared.applicationIconBadgeNumber = Int(APIS.getTotalCounter())
+                            }
                         } else {
                         } else {
-                            completionHandler(.failed)
+                            let ret = retry + 1
+                            if ret <= 5 {
+                                getMessageById(id: id, retry: ret, completionHandler: completionHandler)
+                            } else {
+                                completionHandler(.failed)
+                            }
                         }
                         }
                     }
                     }
-                } else {
+                } catch {
                     let ret = retry + 1
                     let ret = retry + 1
                     if ret <= 5 {
                     if ret <= 5 {
                         getMessageById(id: id, retry: ret, completionHandler: completionHandler)
                         getMessageById(id: id, retry: ret, completionHandler: completionHandler)
@@ -1792,6 +1790,13 @@ public class APIS: NSObject {
                         completionHandler(.failed)
                         completionHandler(.failed)
                     }
                     }
                 }
                 }
+            } else {
+                let ret = retry + 1
+                if ret <= 5 {
+                    getMessageById(id: id, retry: ret, completionHandler: completionHandler)
+                } else {
+                    completionHandler(.failed)
+                }
             }
             }
         }
         }
     }
     }

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

@@ -19,7 +19,7 @@ import CryptoKit
 import WebKit
 import WebKit
 
 
 public class Nexilis: NSObject {
 public class Nexilis: NSObject {
-    public static var cpaasVersion = "5.0.61"
+    public static var cpaasVersion = "5.0.63"
     public static var sAPIKey = ""
     public static var sAPIKey = ""
     
     
     public static var ADDRESS = ""
     public static var ADDRESS = ""
@@ -477,7 +477,7 @@ public class Nexilis: NSObject {
     }
     }
     
     
     private static func getPullPrefs() {
     private static func getPullPrefs() {
-        DispatchQueue.global().async {
+        DispatchQueue.global(qos: .userInitiated).async {
             let urlString = Utils.getBEId().isEmpty ? Utils.getDomainOpr() + "nexilis/logics/get_baseurl_new?key=\(Nexilis.sAPIKey)" : Utils.getDomainOpr() + "nexilis/logics/get_prefs?be=\(Utils.getBEId())&appId=\(APIS.getAppNm().toStupidString())"
             let urlString = Utils.getBEId().isEmpty ? Utils.getDomainOpr() + "nexilis/logics/get_baseurl_new?key=\(Nexilis.sAPIKey)" : Utils.getDomainOpr() + "nexilis/logics/get_prefs?be=\(Utils.getBEId())&appId=\(APIS.getAppNm().toStupidString())"
             Utils.fetchDataWithCookiesAndUserAgent(from: URL(string: urlString)!) { data, response, error in
             Utils.fetchDataWithCookiesAndUserAgent(from: URL(string: urlString)!) { data, response, error in
                 if let data = data, let responseString = String(data: data, encoding: .utf8) {
                 if let data = data, let responseString = String(data: data, encoding: .utf8) {
@@ -580,7 +580,7 @@ public class Nexilis: NSObject {
             return
             return
         }
         }
         isGettingFeatureAccess = true
         isGettingFeatureAccess = true
-        DispatchQueue.global().async {
+        DispatchQueue.global(qos: .userInitiated).async {
             if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.getFeatureAccessAll(), timeout: 10000), response.isOk() {
             if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.getFeatureAccessAll(), timeout: 10000), response.isOk() {
                 let data = response.getBody(key: CoreMessage_TMessageKey.DATA, default_value: "[]")
                 let data = response.getBody(key: CoreMessage_TMessageKey.DATA, default_value: "[]")
                 do {
                 do {

+ 168 - 81
NexilisLite/NexilisLite/Source/Utils.swift

@@ -3781,101 +3781,188 @@ class HtmlUtils {
     }
     }
 }
 }
 
 
-class FormView: UIView {
+enum FormFieldType: String {
+    case dateChooser
+    case dateTimeChooser
+    case timeChooser
+    case itemChooser
+    case inputRadio
+    case inputRadioHorizontal
+    case inputNumber
+    case inputText
+    case inputTextMultiline
+    case inputCheck
+    case inputFile
+    case inputPhoto
+    case inputProject
+    case header
+    case transId
+    case transStatus
+    case transAssigned
+    case signature
+    case image
+    case video
+}
+
+// Factory untuk membuat view sesuai tipe
+class FormViewFactory {
+    
+    static func createView(
+        type: FormFieldType,
+        key: String,
+        keyLabel: String,
+        valueLabel: String,
+        background: UIColor? = nil,
+        color: UIColor? = nil
+    ) -> UIView {
+        
+        var result: UIView
+        
+        switch type {
+        case .dateChooser:
+            result = createDateChooser(keyLabel: keyLabel, valueLabel: valueLabel)
+        case .dateTimeChooser:
+            result = createDateTimeChooser()
+        case .timeChooser:
+            result = createTimeChooser()
+        case .itemChooser:
+            result = createItemChooser(keyLabel: keyLabel, valueLabel: valueLabel)
+        case .inputRadio:
+            result = createRadio(keyLabel: keyLabel, valueLabel: valueLabel, color: color)
+        case .inputRadioHorizontal:
+            result = createRadioHorizontal(keyLabel: keyLabel, valueLabel: valueLabel, color: color)
+        case .inputNumber:
+            result = createNumberField(keyLabel: keyLabel, valueLabel: valueLabel)
+        case .inputText:
+            result = createTextField(keyLabel: keyLabel, valueLabel: valueLabel)
+        case .inputTextMultiline:
+            result = createMultilineTextField(keyLabel: keyLabel, valueLabel: valueLabel)
+        case .inputCheck:
+            result = createCheckbox(keyLabel: keyLabel, valueLabel: valueLabel)
+        case .inputFile:
+            result = createButton(title: "Upload File")
+        case .inputPhoto:
+            result = createButton(title: "Take Photo")
+        case .inputProject:
+            result = createLabel("\(keyLabel): [Project Picker]")
+        case .header:
+            result = createHeader(title: keyLabel)
+        case .transId:
+            result = createLabel("Transaction ID: \(valueLabel)")
+        case .transStatus:
+            result = createLabel("Status: \(keyLabel)")
+        case .transAssigned:
+            result = createLabel("Assigned to: \(valueLabel)")
+        case .signature:
+            result = createButton(title: "Add Signature")
+        case .image:
+            result = createButton(title: "Pick Image")
+        case .video:
+            result = createButton(title: "Pick Video")
+        }
+        
+        // optional background
+        if let bg = background {
+            result.backgroundColor = bg
+        }
+        
+        return result
+    }
     
     
-    private var scrollView: UIScrollView!
-    private var stackView: UIStackView!
+    // MARK: - Builder sederhana
     
     
-    private var resetButton: UIButton!
-    private var submitButton: UIButton!
-    private var rejectButton: UIButton!
-    private var approveButton: UIButton!
+    private static func createLabel(_ text: String) -> UIView {
+        let label = UILabel()
+        label.text = text
+        return label
+    }
     
     
-    override init(frame: CGRect) {
-        super.init(frame: frame)
-        setupView()
+    private static func createHeader(title: String) -> UIView {
+        let label = UILabel()
+        label.text = title
+        label.font = UIFont.boldSystemFont(ofSize: 18)
+        return label
     }
     }
     
     
-    required init?(coder: NSCoder) {
-        super.init(coder: coder)
-        setupView()
+    private static func createButton(title: String) -> UIView {
+        let button = UIButton(type: .system)
+        button.setTitle(title, for: .normal)
+        return button
     }
     }
     
     
-    private func setupView() {
-        backgroundColor = .white
-        
-        // ScrollView + Stack
-        scrollView = UIScrollView()
-        scrollView.translatesAutoresizingMaskIntoConstraints = false
-        addSubview(scrollView)
-        
-        stackView = UIStackView()
-        stackView.axis = .vertical
-        stackView.spacing = 10
-        stackView.translatesAutoresizingMaskIntoConstraints = false
-        scrollView.addSubview(stackView)
-        
-        // Buttons
-        resetButton = createButton(title: "Reset".localized(), color: .gray, action: #selector(resetTapped))
-        submitButton = createButton(title: "Submit".localized(), color: .systemBlue, action: #selector(submitTapped))
-        rejectButton = createButton(title: "Reject".localized(), color: .gray, action: #selector(rejectTapped))
-        approveButton = createButton(title: "Approve".localized(), color: .systemBlue, action: #selector(approveTapped))
-        
-        let buttonStack = UIStackView(arrangedSubviews: [resetButton, rejectButton, submitButton, approveButton])
-        buttonStack.axis = .horizontal
-        buttonStack.spacing = 8
-        buttonStack.distribution = .fillEqually
-        buttonStack.translatesAutoresizingMaskIntoConstraints = false
-        addSubview(buttonStack)
-        
-        // Layout
-        NSLayoutConstraint.activate([
-            scrollView.topAnchor.constraint(equalTo: topAnchor),
-            scrollView.leftAnchor.constraint(equalTo: leftAnchor),
-            scrollView.rightAnchor.constraint(equalTo: rightAnchor),
-            scrollView.bottomAnchor.constraint(equalTo: buttonStack.topAnchor, constant: -10),
-            
-            stackView.topAnchor.constraint(equalTo: scrollView.topAnchor),
-            stackView.leftAnchor.constraint(equalTo: scrollView.leftAnchor),
-            stackView.rightAnchor.constraint(equalTo: scrollView.rightAnchor),
-            stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
-            stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
-            
-            buttonStack.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -8),
-            buttonStack.leftAnchor.constraint(equalTo: leftAnchor, constant: 8),
-            buttonStack.rightAnchor.constraint(equalTo: rightAnchor, constant: -8),
-            buttonStack.heightAnchor.constraint(equalToConstant: 50)
-        ])
+    private static func createTextField(keyLabel: String, valueLabel: String) -> UIView {
+        let textField = UITextField()
+        textField.placeholder = keyLabel
+        textField.text = valueLabel
+        textField.borderStyle = .roundedRect
+        return textField
     }
     }
     
     
-    private func createButton(title: String, color: UIColor, action: Selector) -> UIButton {
+    private static func createMultilineTextField(keyLabel: String, valueLabel: String) -> UIView {
+        let textView = UITextView()
+        textView.text = valueLabel.isEmpty ? keyLabel : valueLabel
+        textView.layer.borderWidth = 1
+        textView.layer.borderColor = UIColor.gray.cgColor
+        textView.layer.cornerRadius = 6
+        return textView
+    }
+    
+    private static func createNumberField(keyLabel: String, valueLabel: String) -> UIView {
+        let textField = createTextField(keyLabel: keyLabel, valueLabel: valueLabel) as! UITextField
+        textField.keyboardType = .numberPad
+        return textField
+    }
+    
+    private static func createDateChooser(keyLabel: String, valueLabel: String) -> UIView {
+        let picker = UIDatePicker()
+        picker.datePickerMode = .date
+        return picker
+    }
+    
+    private static func createDateTimeChooser() -> UIView {
+        let picker = UIDatePicker()
+        picker.datePickerMode = .dateAndTime
+        return picker
+    }
+    
+    private static func createTimeChooser() -> UIView {
+        let picker = UIDatePicker()
+        picker.datePickerMode = .time
+        return picker
+    }
+    
+    private static func createItemChooser(keyLabel: String, valueLabel: String) -> UIView {
+        return createButton(title: "\(keyLabel): \(valueLabel)")
+    }
+    
+    private static func createRadio(keyLabel: String, valueLabel: String, color: UIColor?) -> UIView {
         let button = UIButton(type: .system)
         let button = UIButton(type: .system)
-        button.setTitle(title, for: .normal)
-        button.backgroundColor = color
-        button.setTitleColor(.white, for: .normal)
-        button.layer.cornerRadius = 8
-        button.addTarget(self, action: action, for: .touchUpInside)
+        button.setTitle("○ \(valueLabel)", for: .normal)
+        button.tintColor = color ?? .blue
         return button
         return button
     }
     }
     
     
-    // MARK: - Actions
-    @objc private func resetTapped() { print("Reset tapped") }
-    @objc private func submitTapped() { print("Submit tapped") }
-    @objc private func rejectTapped() { print("Reject tapped") }
-    @objc private func approveTapped() { print("Approve tapped") }
-    
-    // MARK: - Add Dynamic Fields
-    func addField(_ view: UIView) {
-        stackView.addArrangedSubview(view)
+    private static func createRadioHorizontal(keyLabel: String, valueLabel: String, color: UIColor?) -> UIView {
+        let stack = UIStackView()
+        stack.axis = .horizontal
+        stack.spacing = 8
+        
+        let label = UILabel()
+        label.text = keyLabel
+        
+        let button = UIButton(type: .system)
+        button.setTitle(valueLabel, for: .normal)
+        button.tintColor = color ?? .blue
+        
+        stack.addArrangedSubview(label)
+        stack.addArrangedSubview(button)
+        return stack
     }
     }
-}
-
-// MARK: - UILabel convenience init
-extension UILabel {
-    convenience init(text: String) {
-        self.init()
-        self.text = text
-        self.font = UIFont.systemFont(ofSize: 14)
+    
+    private static func createCheckbox(keyLabel: String, valueLabel: String) -> UIView {
+        let button = UIButton(type: .system)
+        button.setTitle("☐ \(keyLabel)", for: .normal)
+        return button
     }
     }
 }
 }
 
 

+ 70 - 2
NexilisLite/NexilisLite/Source/View/Control/ChangeDeviceViewController.swift

@@ -305,7 +305,41 @@ public class ChangeDeviceViewController: UIViewController {
             }
             }
             KeyManagerNexilis.generateKey()
             KeyManagerNexilis.generateKey()
             KeyManagerNexilis.saveMarker()
             KeyManagerNexilis.saveMarker()
-            guard let privateKey = KeyManagerNexilis.getPrivateKey() else {
+            let dataTxn = Utils.getTxnLevel()
+            var policyLevel = "1,2"
+            if !dataTxn.isEmpty {
+                if let data = dataTxn.data(using: .utf8) {
+                    do {
+                        // Parse to generic JSON array
+                        if let jsonArray = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]] {
+                            for json in jsonArray {
+                                let min = json["min"] as? Double ?? 0
+                                let max = json["max"] as? Double ?? 0
+                                let policy = json["policy"] as? String ?? ""
+                                let amount = 0.0
+                                if max == -1 {
+                                    if amount >= min {
+                                        policyLevel = policy
+                                        break
+                                    }
+                                } else {
+                                    if amount >= min && amount <= max {
+                                        policyLevel = policy
+                                        break
+                                    }
+                                }
+                            }
+                        }
+                    } catch {
+                        print("Error converting string to JSONArray:", error)
+                    }
+                }
+            }
+            var isBiometricOn = false
+            if policyLevel == MFAViewController.STEP_FIDO_PWD_BIOFINGER || policyLevel == MFAViewController.STEP_FIDO_PWD_BIOFACE || policyLevel == MFAViewController.STEP_FIDO_BIOFINGER || policyLevel == MFAViewController.STEP_FIDO_BIOFACE {
+                isBiometricOn = true
+            }
+            guard let privateKey = KeyManagerNexilis.getPrivateKey(useBiometric: isBiometricOn) else {
                 KeyManagerNexilis.deleteKey()
                 KeyManagerNexilis.deleteKey()
                 KeyManagerNexilis.deleteMarker()
                 KeyManagerNexilis.deleteMarker()
                 UIApplication.shared.visibleViewController?.view.makeToast("Biometric or passcode authentication required".localized(), duration: 3, position: .center)
                 UIApplication.shared.visibleViewController?.view.makeToast("Biometric or passcode authentication required".localized(), duration: 3, position: .center)
@@ -440,7 +474,41 @@ public class ChangeDeviceViewController: UIViewController {
         }
         }
         KeyManagerNexilis.generateKey()
         KeyManagerNexilis.generateKey()
         KeyManagerNexilis.saveMarker()
         KeyManagerNexilis.saveMarker()
-        guard let privateKey = KeyManagerNexilis.getPrivateKey() else {
+        let dataTxn = Utils.getTxnLevel()
+        var policyLevel = "1,2"
+        if !dataTxn.isEmpty {
+            if let data = dataTxn.data(using: .utf8) {
+                do {
+                    // Parse to generic JSON array
+                    if let jsonArray = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]] {
+                        for json in jsonArray {
+                            let min = json["min"] as? Double ?? 0
+                            let max = json["max"] as? Double ?? 0
+                            let policy = json["policy"] as? String ?? ""
+                            let amount = 0.0
+                            if max == -1 {
+                                if amount >= min {
+                                    policyLevel = policy
+                                    break
+                                }
+                            } else {
+                                if amount >= min && amount <= max {
+                                    policyLevel = policy
+                                    break
+                                }
+                            }
+                        }
+                    }
+                } catch {
+                    print("Error converting string to JSONArray:", error)
+                }
+            }
+        }
+        var isBiometricOn = false
+        if policyLevel == MFAViewController.STEP_FIDO_PWD_BIOFINGER || policyLevel == MFAViewController.STEP_FIDO_PWD_BIOFACE || policyLevel == MFAViewController.STEP_FIDO_BIOFINGER || policyLevel == MFAViewController.STEP_FIDO_BIOFACE {
+            isBiometricOn = true
+        }
+        guard let privateKey = KeyManagerNexilis.getPrivateKey(useBiometric: isBiometricOn) else {
             KeyManagerNexilis.deleteKey()
             KeyManagerNexilis.deleteKey()
             KeyManagerNexilis.deleteMarker()
             KeyManagerNexilis.deleteMarker()
             UIApplication.shared.visibleViewController?.view.makeToast("Biometric or passcode authentication required".localized(), duration: 3, position: .center)
             UIApplication.shared.visibleViewController?.view.makeToast("Biometric or passcode authentication required".localized(), duration: 3, position: .center)

+ 59 - 145
NexilisLite/NexilisLite/Source/View/Control/SettingTableViewController.swift

@@ -109,182 +109,96 @@ public class SettingTableViewController: UITableViewController, UIGestureRecogni
         SecureUserDefaults.shared.set(switchAutoDownload.isOn, forKey: "autoDownload")
         SecureUserDefaults.shared.set(switchAutoDownload.isOn, forKey: "autoDownload")
     }
     }
     
     
-    func makeMenu(imageSignIn: String = ""){
+    func makeMenu(){
         let isChangeProfile = Utils.getSetProfile()
         let isChangeProfile = Utils.getSetProfile()
         if Database.shared.database == nil {
         if Database.shared.database == nil {
             Item.menus["Personal"] = [
             Item.menus["Personal"] = [
                 Item(icon: UIImage(systemName: "person"), title: "Personal Information".localized()),
                 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: "arrow.up.and.person.rectangle.portrait"), title: "Sign-Up/Sign-In".localized()),
                 Item(icon: UIImage(systemName: "arrow.up.and.person.rectangle.portrait"), title: "Sign-Up/Sign-In".localized()),
             ]
             ]
         } else {
         } else {
             Database.shared.database?.inTransaction({ fmdb, rollback in
             Database.shared.database?.inTransaction({ fmdb, rollback in
-                do {
-                    let idMe = User.getMyPin() as String?
-                    if let cursorUser = Database.shared.getRecords(fmdb: fmdb, query: "SELECT user_type, image_id, official_account FROM BUDDY where f_pin='\(idMe!)'"), cursorUser.next() {
-                        if (User.isInternal(userType: cursorUser.string(forColumnIndex: 0) ?? "") && User.isAdmin(fmdb: fmdb)) || User.isOfficial(official_account: cursorUser.string(forColumnIndex: 2) ?? "") || User.isOfficial(official_account: cursorUser.string(forColumnIndex: 2) ?? "") {
-                            Item.menus["Personal"] = [
-                                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: "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()),
-                                Item(icon: UIImage(named: "pb_call_center", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, title: "Set CS Account".localized()),
-                            ]
-                        } else if User.isInternal(userType: cursorUser.string(forColumnIndex: 0) ?? "") || User.isCallCenter(userType: cursorUser.string(forColumnIndex: 0) ?? "") || User.isVerified(official_account: cursorUser.string(forColumnIndex: 2) ?? "") {
-                            Item.menus["Personal"] = [
-                                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: "laptopcomputer.and.iphone"), title: "Sign-In to Web".localized()),
-                            ]
-                        } else {
-                            Item.menus["Personal"] = [
-                                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: "person.badge.key"), title: "Access Admin / Internal Features".localized()),
-                            ]
-                        }
-                        if !isChangeProfile {
-                            Item.menus["Personal"]?.append(Item(icon: UIImage(systemName: "arrow.up.and.person.rectangle.portrait"), title: "Sign-Up/Sign-In".localized()))
-                        } else if isChangeProfile {
-                            if Nexilis.checkingAccess(key: "backup_restore") {
-                                Item.menus["Personal"]?.append(Item(icon: UIImage(systemName: "arrow.clockwise.icloud"), title: "Backup & Restore".localized()))
-                            }
-                            if Utils.getLimitValidTrans() == "1" {
-                                Item.menus["Personal"]?.insert(Item(icon: UIImage(systemName: "lessthan.circle"), title: "Validation Transaction Limit".localized()), at: 1)
-                            }
-                        }
-                        let image = cursorUser.string(forColumnIndex: 1)
-                        if image != nil {
-                            if !image!.isEmpty {
-                                do {
-                                    let documentDir = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
-                                    let file = documentDir.appendingPathComponent(image!)
-                                    if FileManager().fileExists(atPath: file.path) {
-                                        let image = UIImage(contentsOfFile: file.path)
-                                        Item.menus["Personal"]?[0].icon = image?.circleMasked
-                                        if !imageSignIn.isEmpty {
-                                            var dataImage: [AnyHashable : Any] = [:]
-                                            dataImage["name"] = imageSignIn
-                                            NotificationCenter.default.post(name: NSNotification.Name(rawValue: "imageFBUpdate"), object: nil, userInfo: dataImage)
-                                        }
-                                    } else if FileEncryption.shared.isSecureExists(filename: imageSignIn) {
-                                        do {
-                                            if var data = try FileEncryption.shared.readSecure(filename: imageSignIn) {
-                                                let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
-                                                if dataDecrypt != nil {
-                                                    data = dataDecrypt!
-                                                }
-                                                let image = UIImage(data: data)
-                                                Item.menus["Personal"]?[0].icon = image?.circleMasked
-                                                if !imageSignIn.isEmpty {
-                                                    var dataImage: [AnyHashable : Any] = [:]
-                                                    dataImage["name"] = imageSignIn
-                                                    NotificationCenter.default.post(name: NSNotification.Name(rawValue: "imageFBUpdate"), object: nil, userInfo: dataImage)
-                                                }
-                                            }
-                                        } catch {
-                                            
-                                        }
-                                    } else {
-                                        Download().startHTTP(forKey: image!) { (name, progress) in
-                                            guard progress == 100 else {
-                                                return
-                                            }
-
-                                            DispatchQueue.main.async {
-                                                if FileEncryption.shared.isSecureExists(filename: imageSignIn) {
-                                                    do {
-                                                        if var data = try FileEncryption.shared.readSecure(filename: imageSignIn) {
-                                                            let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
-                                                            if dataDecrypt != nil {
-                                                                data = dataDecrypt!
-                                                            }
-                                                            let image = UIImage(data: data)
-                                                            Item.menus["Personal"]?[0].icon = image?.circleMasked
-                                                            if !imageSignIn.isEmpty {
-                                                                var dataImage: [AnyHashable : Any] = [:]
-                                                                dataImage["name"] = imageSignIn
-                                                                NotificationCenter.default.post(name: NSNotification.Name(rawValue: "imageFBUpdate"), object: nil, userInfo: dataImage)
-                                                            }
-                                                        }
-                                                    } catch {
-                                                        
-                                                    }
-                                                }
-                                            }
-                                        }
-                                    }
-                                } catch {}
-                            }
-                        }
-                        cursorUser.close()
+                let idMe = User.getMyPin() as String?
+                if let cursorUser = Database.shared.getRecords(fmdb: fmdb, query: "SELECT user_type, image_id, official_account FROM BUDDY where f_pin='\(idMe!)'"), cursorUser.next() {
+                    if (User.isInternal(userType: cursorUser.string(forColumnIndex: 0) ?? "") && User.isAdmin(fmdb: fmdb)) || User.isOfficial(official_account: cursorUser.string(forColumnIndex: 2) ?? "") || User.isOfficial(official_account: cursorUser.string(forColumnIndex: 2) ?? "") {
+                        Item.menus["Personal"] = [
+                            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: "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()),
+                            Item(icon: UIImage(named: "pb_call_center", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, title: "Set CS Account".localized()),
+                        ]
+                    } else if User.isInternal(userType: cursorUser.string(forColumnIndex: 0) ?? "") || User.isCallCenter(userType: cursorUser.string(forColumnIndex: 0) ?? "") || User.isVerified(official_account: cursorUser.string(forColumnIndex: 2) ?? "") {
+                        Item.menus["Personal"] = [
+                            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: "laptopcomputer.and.iphone"), title: "Sign-In to Web".localized()),
+                        ]
                     } else {
                     } else {
                         Item.menus["Personal"] = [
                         Item.menus["Personal"] = [
                             Item(icon: UIImage(systemName: "person"), title: "Personal Information".localized()),
                             Item(icon: UIImage(systemName: "person"), title: "Personal Information".localized()),
-                                Item(icon: UIImage(systemName: "textformat.abc"), title: "Change Language".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: "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()),
 //                            Item(icon: UIImage(systemName: "person.badge.key"), title: "Access Admin / Internal Features".localized()),
                         ]
                         ]
+                    }
+                    if !isChangeProfile {
                         Item.menus["Personal"]?.append(Item(icon: UIImage(systemName: "arrow.up.and.person.rectangle.portrait"), title: "Sign-Up/Sign-In".localized()))
                         Item.menus["Personal"]?.append(Item(icon: UIImage(systemName: "arrow.up.and.person.rectangle.portrait"), title: "Sign-Up/Sign-In".localized()))
-                        if !imageSignIn.isEmpty {
+                    } else if isChangeProfile {
+                        if Nexilis.checkingAccess(key: "backup_restore") {
+                            Item.menus["Personal"]?.append(Item(icon: UIImage(systemName: "arrow.clockwise.icloud"), title: "Backup & Restore".localized()))
+                        }
+                        if Utils.getLimitValidTrans() == "1" {
+                            Item.menus["Personal"]?.insert(Item(icon: UIImage(systemName: "lessthan.circle"), title: "Validation Transaction Limit".localized()), at: 1)
+                        }
+                    }
+                    let image = cursorUser.string(forColumnIndex: 1)
+                    if image != nil {
+                        if !image!.isEmpty {
                             do {
                             do {
                                 let documentDir = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
                                 let documentDir = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
-                                let file = documentDir.appendingPathComponent(imageSignIn)
+                                let file = documentDir.appendingPathComponent(image!)
                                 if FileManager().fileExists(atPath: file.path) {
                                 if FileManager().fileExists(atPath: file.path) {
                                     let image = UIImage(contentsOfFile: file.path)
                                     let image = UIImage(contentsOfFile: file.path)
                                     Item.menus["Personal"]?[0].icon = image?.circleMasked
                                     Item.menus["Personal"]?[0].icon = image?.circleMasked
-                                    var dataImage: [AnyHashable : Any] = [:]
-                                    dataImage["name"] = imageSignIn
-                                    NotificationCenter.default.post(name: NSNotification.Name(rawValue: "imageFBUpdate"), object: nil, userInfo: dataImage)
-                                } else if FileEncryption.shared.isSecureExists(filename: imageSignIn) {
+                                } else if FileEncryption.shared.isSecureExists(filename: image!) {
                                     do {
                                     do {
-                                        if var data = try FileEncryption.shared.readSecure(filename: imageSignIn) {
+                                        if var data = try FileEncryption.shared.readSecure(filename: image!) {
                                             let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
                                             let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
                                             if dataDecrypt != nil {
                                             if dataDecrypt != nil {
                                                 data = dataDecrypt!
                                                 data = dataDecrypt!
                                             }
                                             }
                                             let image = UIImage(data: data)
                                             let image = UIImage(data: data)
                                             Item.menus["Personal"]?[0].icon = image?.circleMasked
                                             Item.menus["Personal"]?[0].icon = image?.circleMasked
-                                            if !imageSignIn.isEmpty {
-                                                var dataImage: [AnyHashable : Any] = [:]
-                                                dataImage["name"] = imageSignIn
-                                                NotificationCenter.default.post(name: NSNotification.Name(rawValue: "imageFBUpdate"), object: nil, userInfo: dataImage)
-                                            }
                                         }
                                         }
                                     } catch {
                                     } catch {
                                         
                                         
                                     }
                                     }
                                 } else {
                                 } else {
-                                    Download().startHTTP(forKey: imageSignIn) { (name, progress) in
+                                    Download().startHTTP(forKey: image!) { (name, progress) in
                                         guard progress == 100 else {
                                         guard progress == 100 else {
                                             return
                                             return
                                         }
                                         }
+                                        
                                         DispatchQueue.main.async {
                                         DispatchQueue.main.async {
-                                            if FileEncryption.shared.isSecureExists(filename: imageSignIn) {
+                                            if FileEncryption.shared.isSecureExists(filename: image!) {
                                                 do {
                                                 do {
-                                                    if var data = try FileEncryption.shared.readSecure(filename: imageSignIn) {
+                                                    if var data = try FileEncryption.shared.readSecure(filename: image!) {
                                                         let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
                                                         let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
                                                         if dataDecrypt != nil {
                                                         if dataDecrypt != nil {
                                                             data = dataDecrypt!
                                                             data = dataDecrypt!
                                                         }
                                                         }
                                                         let image = UIImage(data: data)
                                                         let image = UIImage(data: data)
                                                         Item.menus["Personal"]?[0].icon = image?.circleMasked
                                                         Item.menus["Personal"]?[0].icon = image?.circleMasked
-                                                        if !imageSignIn.isEmpty {
-                                                            var dataImage: [AnyHashable : Any] = [:]
-                                                            dataImage["name"] = imageSignIn
-                                                            NotificationCenter.default.post(name: NSNotification.Name(rawValue: "imageFBUpdate"), object: nil, userInfo: dataImage)
-                                                        }
+                                                        self.tableView.reloadData()
                                                     }
                                                     }
                                                 } catch {
                                                 } catch {
                                                     
                                                     
@@ -296,9 +210,7 @@ public class SettingTableViewController: UITableViewController, UIGestureRecogni
                             } catch {}
                             } catch {}
                         }
                         }
                     }
                     }
-                } catch {
-                    rollback.pointee = true
-                    print("Access database error: \(error.localizedDescription)")
+                    cursorUser.close()
                 }
                 }
             })
             })
         }
         }
@@ -314,7 +226,11 @@ public class SettingTableViewController: UITableViewController, UIGestureRecogni
             Item.menus["Config"]?.append(Item(icon: UIImage(systemName: "paintbrush"), title: "Switch Style".localized()))
             Item.menus["Config"]?.append(Item(icon: UIImage(systemName: "paintbrush"), title: "Switch Style".localized()))
         }
         }
         if Utils.getIsLoadThemeFromOther() {
         if Utils.getIsLoadThemeFromOther() {
-            Item.menus["Config"]?.insert(Item(icon: UIImage(systemName: "iphone"), title: "Back to Company App".localized()), at: 1)
+            if Item.menus["Config"]!.count > 0 {
+                Item.menus["Config"]?.insert(Item(icon: UIImage(systemName: "iphone"), title: "Back to Company App".localized()), at: 1)
+            } else {
+                Item.menus["Config"]?.append(Item(icon: UIImage(systemName: "iphone"), title: "Back to Company App".localized()))
+            }
         }
         }
         
         
         Item.menus["Call"] = [
         Item.menus["Call"] = [
@@ -801,16 +717,14 @@ public class SettingTableViewController: UITableViewController, UIGestureRecogni
                                 Utils.setIsLoadThemeFromOther(value: false)
                                 Utils.setIsLoadThemeFromOther(value: false)
                                 Utils.resetValueSuperApp()
                                 Utils.resetValueSuperApp()
                                 Utils.setValueInitialApp(data: Utils.getPrefTheme())
                                 Utils.setValueInitialApp(data: Utils.getPrefTheme())
+                                Utils.setLastTabSelected(value: 0)
+                                Utils.setIsWATheme(value: false)
+                                UIApplication.shared.setAlternateIconName(nil)
                                 Database.shared.database?.inTransaction({ fmdb, rollback in
                                 Database.shared.database?.inTransaction({ fmdb, rollback in
-                                    do {
-                                        _ = Database.shared.deleteRecord(fmdb: fmdb, table: "GROUPZ", _where: "")
-                                        _ = Database.shared.deleteRecord(fmdb: fmdb, table: "GROUPZ_MEMBER", _where: "")
-                                        _ = Database.shared.deleteRecord(fmdb: fmdb, table: "DISCUSSION_FORUM", _where: "")
-                                        _ = Nexilis.write(message: CoreMessage_TMessageBank.getPostRegistration(p_pin: User.getMyPin() ?? ""))
-                                    } catch {
-                                        rollback.pointee = true
-                                        print("Access database error: \(error.localizedDescription)")
-                                    }
+                                    _ = Database.shared.deleteRecord(fmdb: fmdb, table: "GROUPZ", _where: "")
+                                    _ = Database.shared.deleteRecord(fmdb: fmdb, table: "GROUPZ_MEMBER", _where: "")
+                                    _ = Database.shared.deleteRecord(fmdb: fmdb, table: "DISCUSSION_FORUM", _where: "")
+                                    _ = Nexilis.write(message: CoreMessage_TMessageBank.getPostRegistration(p_pin: User.getMyPin() ?? ""))
                                 })
                                 })
                                 Nexilis.hideLoader {
                                 Nexilis.hideLoader {
                                     let alert = LibAlertController(title: "Successfully changed".localized(), message: "Please open the app again to see the changes".localized(), preferredStyle: .alert)
                                     let alert = LibAlertController(title: "Successfully changed".localized(), message: "Please open the app again to see the changes".localized(), preferredStyle: .alert)

+ 70 - 2
NexilisLite/NexilisLite/Source/View/Control/SignUpSignIn.swift

@@ -304,7 +304,41 @@ public class SignUpSignIn: UIViewController {
             }
             }
             KeyManagerNexilis.generateKey()
             KeyManagerNexilis.generateKey()
             KeyManagerNexilis.saveMarker()
             KeyManagerNexilis.saveMarker()
-            guard let privateKey = KeyManagerNexilis.getPrivateKey() else {
+            let dataTxn = Utils.getTxnLevel()
+            var policyLevel = "1,2"
+            if !dataTxn.isEmpty {
+                if let data = dataTxn.data(using: .utf8) {
+                    do {
+                        // Parse to generic JSON array
+                        if let jsonArray = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]] {
+                            for json in jsonArray {
+                                let min = json["min"] as? Double ?? 0
+                                let max = json["max"] as? Double ?? 0
+                                let policy = json["policy"] as? String ?? ""
+                                let amount = 0.0
+                                if max == -1 {
+                                    if amount >= min {
+                                        policyLevel = policy
+                                        break
+                                    }
+                                } else {
+                                    if amount >= min && amount <= max {
+                                        policyLevel = policy
+                                        break
+                                    }
+                                }
+                            }
+                        }
+                    } catch {
+                        print("Error converting string to JSONArray:", error)
+                    }
+                }
+            }
+            var isBiometricOn = false
+            if policyLevel == MFAViewController.STEP_FIDO_PWD_BIOFINGER || policyLevel == MFAViewController.STEP_FIDO_PWD_BIOFACE || policyLevel == MFAViewController.STEP_FIDO_BIOFINGER || policyLevel == MFAViewController.STEP_FIDO_BIOFACE {
+                isBiometricOn = true
+            }
+            guard let privateKey = KeyManagerNexilis.getPrivateKey(useBiometric: isBiometricOn) else {
                 KeyManagerNexilis.deleteKey()
                 KeyManagerNexilis.deleteKey()
                 KeyManagerNexilis.deleteMarker()
                 KeyManagerNexilis.deleteMarker()
                 UIApplication.shared.visibleViewController?.view.makeToast("Biometric or passcode authentication required".localized(), duration: 3, position: .center)
                 UIApplication.shared.visibleViewController?.view.makeToast("Biometric or passcode authentication required".localized(), duration: 3, position: .center)
@@ -459,7 +493,41 @@ public class SignUpSignIn: UIViewController {
         }
         }
         KeyManagerNexilis.generateKey()
         KeyManagerNexilis.generateKey()
         KeyManagerNexilis.saveMarker()
         KeyManagerNexilis.saveMarker()
-        guard let privateKey = KeyManagerNexilis.getPrivateKey() else {
+        let dataTxn = Utils.getTxnLevel()
+        var policyLevel = "1,2"
+        if !dataTxn.isEmpty {
+            if let data = dataTxn.data(using: .utf8) {
+                do {
+                    // Parse to generic JSON array
+                    if let jsonArray = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]] {
+                        for json in jsonArray {
+                            let min = json["min"] as? Double ?? 0
+                            let max = json["max"] as? Double ?? 0
+                            let policy = json["policy"] as? String ?? ""
+                            let amount = 0.0
+                            if max == -1 {
+                                if amount >= min {
+                                    policyLevel = policy
+                                    break
+                                }
+                            } else {
+                                if amount >= min && amount <= max {
+                                    policyLevel = policy
+                                    break
+                                }
+                            }
+                        }
+                    }
+                } catch {
+                    print("Error converting string to JSONArray:", error)
+                }
+            }
+        }
+        var isBiometricOn = false
+        if policyLevel == MFAViewController.STEP_FIDO_PWD_BIOFINGER || policyLevel == MFAViewController.STEP_FIDO_PWD_BIOFACE || policyLevel == MFAViewController.STEP_FIDO_BIOFINGER || policyLevel == MFAViewController.STEP_FIDO_BIOFACE {
+            isBiometricOn = true
+        }
+        guard let privateKey = KeyManagerNexilis.getPrivateKey(useBiometric: isBiometricOn) else {
             KeyManagerNexilis.deleteKey()
             KeyManagerNexilis.deleteKey()
             KeyManagerNexilis.deleteMarker()
             KeyManagerNexilis.deleteMarker()
             UIApplication.shared.visibleViewController?.view.makeToast("Biometric or passcode authentication required".localized(), duration: 3, position: .center)
             UIApplication.shared.visibleViewController?.view.makeToast("Biometric or passcode authentication required".localized(), duration: 3, position: .center)