Explorar el Código

update fix bugs for release 5.0.46

alqindiirsyam hace 1 mes
padre
commit
6088eac847
Se han modificado 23 ficheros con 752 adiciones y 317 borrados
  1. 4 4
      AppBuilder/AppBuilder.xcodeproj/project.pbxproj
  2. 1 0
      AppBuilder/AppBuilder/FirstTabViewController.swift
  3. 1 5
      AppBuilder/AppBuilder/FourthTabViewController.swift
  4. 1 1
      AppBuilder/AppBuilder/SecondTabViewController.swift
  5. 1 0
      AppBuilder/AppBuilder/ThirdTabViewController.swift
  6. 32 2
      NexilisLite/NexilisLite.xcodeproj/project.pbxproj
  7. 20 8
      NexilisLite/NexilisLite/Source/APIS.swift
  8. 1 1
      NexilisLite/NexilisLite/Source/Callback.swift
  9. 4 0
      NexilisLite/NexilisLite/Source/CoreMessage_TMessageBank.swift
  10. 1 1
      NexilisLite/NexilisLite/Source/Extension.swift
  11. 1 0
      NexilisLite/NexilisLite/Source/MasterKeyUtil.swift
  12. 139 4
      NexilisLite/NexilisLite/Source/Nexilis.swift
  13. 1 1
      NexilisLite/NexilisLite/Source/View/Call/QmeraAudioViewController.swift
  14. 5 1
      NexilisLite/NexilisLite/Source/View/Call/QmeraVideoViewController.swift
  15. 80 34
      NexilisLite/NexilisLite/Source/View/Chat/EditorGroup.swift
  16. 18 15
      NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift
  17. 67 51
      NexilisLite/NexilisLite/Source/View/Chat/EditorStarMessages.swift
  18. 339 172
      NexilisLite/NexilisLite/Source/View/Control/ChangeDeviceViewController.swift
  19. 1 1
      NexilisLite/NexilisLite/Source/View/Control/ContactChatViewController.swift
  20. 1 5
      NexilisLite/NexilisLite/Source/View/Control/SettingTableViewController.swift
  21. 30 10
      NexilisLite/NexilisLite/Source/View/Control/SignInOption.swift
  22. 3 1
      NexilisLite/NexilisLite/Source/View/Control/SignUpSignIn.swift
  23. 1 0
      NexilisLite/Podfile

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

@@ -564,7 +564,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 5.0.45;
+				MARKETING_VERSION = 5.0.46;
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
@@ -600,7 +600,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 5.0.45;
+				MARKETING_VERSION = 5.0.46;
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
@@ -636,7 +636,7 @@
 					"@executable_path/../../Frameworks",
 				);
 				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
-				MARKETING_VERSION = 5.0.45;
+				MARKETING_VERSION = 5.0.46;
 				OTHER_CFLAGS = "-fstack-protector-strong";
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder.AppBuilderShare;
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -675,7 +675,7 @@
 					"@executable_path/../../Frameworks",
 				);
 				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
-				MARKETING_VERSION = 5.0.45;
+				MARKETING_VERSION = 5.0.46;
 				OTHER_CFLAGS = "-fstack-protector-strong";
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder.AppBuilderShare;
 				PRODUCT_NAME = "$(TARGET_NAME)";

+ 1 - 0
AppBuilder/AppBuilder/FirstTabViewController.swift

@@ -240,6 +240,7 @@ class FirstTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
     
     @objc func onRefreshWebView(notification: NSNotification) {
         FirstTabViewController.forceRefresh = true
+        FirstTabViewController.showModal = false
     }
     
     @objc func onResumeWebView(notification: NSNotification) {

+ 1 - 5
AppBuilder/AppBuilder/FourthTabViewController.swift

@@ -700,11 +700,7 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
         } else if item.title == "Chat Wallpaper".localized() {
             APIS.openChatWallpaper()
         } else if item.title == "Sign-In".localized() {
-            let controller = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "changeDevice") as! ChangeDeviceViewController
-            controller.isDismiss = { newThumb in
-                self.makeMenu()
-                self.tableView.reloadData()
-            }
+            guard let controller = APIS.getControllerSign(forceSignIn: true) else { return }
             navigationController?.show(controller, sender: nil)
         } else if item.title == "Sign-Up/Sign-In".localized() {
             guard let controller = APIS.getControllerSign() else { return }

+ 1 - 1
AppBuilder/AppBuilder/SecondTabViewController.swift

@@ -781,7 +781,7 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
                 self?.timerReloadData?.invalidate()
                 self?.timerReloadData = nil
                 self?.timerReloadData = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { [weak self] _ in
-                    if !self!.isGettingData {
+                    if self != nil && !self!.isGettingData {
                         self?.getData()
                         self?.timerReloadData = nil
                     }

+ 1 - 0
AppBuilder/AppBuilder/ThirdTabViewController.swift

@@ -244,6 +244,7 @@ class ThirdTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
     
     @objc func onRefreshWebView(notification: NSNotification) {
         ThirdTabViewController.forceRefresh = true
+        ThirdTabViewController.showModal = false
     }
     
     @objc func onResumeWebView(notification: NSNotification) {

+ 32 - 2
NexilisLite/NexilisLite.xcodeproj/project.pbxproj

@@ -111,6 +111,11 @@
 		CD1E72612A0BA86100BF871F /* CoreMessage_TMessageCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1E71B32A0BA86100BF871F /* CoreMessage_TMessageCode.swift */; };
 		CD1E72622A0BA86100BF871F /* IncomingThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1E71B42A0BA86100BF871F /* IncomingThread.swift */; };
 		CD1E72632A0BA86100BF871F /* CoreMessage_TMessageUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1E71B52A0BA86100BF871F /* CoreMessage_TMessageUtil.swift */; };
+		CD21CA552E0AA07700DA6B77 /* CommunityNew.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD21CA542E0AA07700DA6B77 /* CommunityNew.swift */; };
+		CD21CA562E0AA07700DA6B77 /* ArchivedChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD21CA522E0AA07700DA6B77 /* ArchivedChatView.swift */; };
+		CD21CA572E0AA07700DA6B77 /* CommunityList.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD21CA532E0AA07700DA6B77 /* CommunityList.swift */; };
+		CD21CA592E0AA0A200DA6B77 /* SignInOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD21CA582E0AA0A200DA6B77 /* SignInOption.swift */; };
+		CD21CA5B2E0AA0C600DA6B77 /* CommunityModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD21CA5A2E0AA0C600DA6B77 /* CommunityModel.swift */; };
 		CD2585C42D59DA60002AB416 /* Dutifully.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = CD2585B72D59DA60002AB416 /* Dutifully.mp3 */; };
 		CD2585C52D59DA60002AB416 /* Juntos.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = CD2585BB2D59DA60002AB416 /* Juntos.mp3 */; };
 		CD2585C62D59DA60002AB416 /* Light_Hearted.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = CD2585BC2D59DA60002AB416 /* Light_Hearted.mp3 */; };
@@ -326,7 +331,6 @@
 		CD1E716B2A0BA86100BF871F /* WorkingAreaPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WorkingAreaPicker.swift; sourceTree = "<group>"; };
 		CD1E716C2A0BA86100BF871F /* BNIBookingWebView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BNIBookingWebView.swift; sourceTree = "<group>"; };
 		CD1E716E2A0BA86100BF871F /* EditorStarMessages.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditorStarMessages.swift; sourceTree = "<group>"; };
-		CD1E716F2A0BA86100BF871F /* PreviewAttachmentImageVideo.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PreviewAttachmentImageVideo.xib; sourceTree = "<group>"; };
 		CD1E71702A0BA86100BF871F /* EditorPersonal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditorPersonal.swift; sourceTree = "<group>"; };
 		CD1E71712A0BA86100BF871F /* FormEditor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormEditor.swift; sourceTree = "<group>"; };
 		CD1E71722A0BA86100BF871F /* EditorGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditorGroup.swift; sourceTree = "<group>"; };
@@ -398,6 +402,11 @@
 		CD1E79142A0CA43600BF871F /* SDWebImage.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SDWebImage.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		CD1E79152A0CA43600BF871F /* SnapKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SnapKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		CD1E79162A0CA43600BF871F /* Toast_Swift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Toast_Swift.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+		CD21CA522E0AA07700DA6B77 /* ArchivedChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArchivedChatView.swift; sourceTree = "<group>"; };
+		CD21CA532E0AA07700DA6B77 /* CommunityList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommunityList.swift; sourceTree = "<group>"; };
+		CD21CA542E0AA07700DA6B77 /* CommunityNew.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommunityNew.swift; sourceTree = "<group>"; };
+		CD21CA582E0AA0A200DA6B77 /* SignInOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInOption.swift; sourceTree = "<group>"; };
+		CD21CA5A2E0AA0C600DA6B77 /* CommunityModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommunityModel.swift; sourceTree = "<group>"; };
 		CD2585B72D59DA60002AB416 /* Dutifully.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = Dutifully.mp3; sourceTree = "<group>"; };
 		CD2585B82D59DA60002AB416 /* Elegant.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = Elegant.mp3; sourceTree = "<group>"; };
 		CD2585B92D59DA60002AB416 /* Eventually.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = Eventually.mp3; sourceTree = "<group>"; };
@@ -680,6 +689,7 @@
 		CD1E715E2A0BA86100BF871F /* Model */ = {
 			isa = PBXGroup;
 			children = (
+				CD21CA5A2E0AA0C600DA6B77 /* CommunityModel.swift */,
 				CD63125C2DA925960088964E /* CallModel.swift */,
 				CD1E71622A0BA86100BF871F /* CategoryCC.swift */,
 				CD1E71602A0BA86100BF871F /* Chat.swift */,
@@ -726,6 +736,9 @@
 		CD1E716D2A0BA86100BF871F /* Chat */ = {
 			isa = PBXGroup;
 			children = (
+				CD21CA522E0AA07700DA6B77 /* ArchivedChatView.swift */,
+				CD21CA532E0AA07700DA6B77 /* CommunityList.swift */,
+				CD21CA542E0AA07700DA6B77 /* CommunityNew.swift */,
 				CD7054E92AD39DE2003741BF /* ChatGPTBotView.swift */,
 				CD63125A2DA925320088964E /* ChatWALikeVC.swift */,
 				CD1E71742A0BA86100BF871F /* CustomTextView.swift */,
@@ -736,7 +749,6 @@
 				CD5A73AD2A77642D000541A5 /* ListGroupImages.swift */,
 				CD5A73AC2A77642D000541A5 /* MessageInfo.swift */,
 				CD1E71732A0BA86100BF871F /* PreviewAttachmentImageVideo.swift */,
-				CD1E716F2A0BA86100BF871F /* PreviewAttachmentImageVideo.xib */,
 				12C36CEA2D02995F0095BEC1 /* SecureFolderView.swift */,
 			);
 			path = Chat;
@@ -782,6 +794,7 @@
 		CD1E71892A0BA86100BF871F /* Control */ = {
 			isa = PBXGroup;
 			children = (
+				CD21CA582E0AA0A200DA6B77 /* SignInOption.swift */,
 				1258BCB52D82D71200EFF5BD /* ChatWallpaperViewController.swift */,
 				CD1E71AB2A0BA86100BF871F /* AddFriendTableViewController.swift */,
 				CD1E71AD2A0BA86100BF871F /* AudienceViewController.swift */,
@@ -1260,10 +1273,14 @@
 			inputFileListPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-NexilisLite/Pods-NexilisLite-resources-${CONFIGURATION}-input-files.xcfilelist",
 			);
+			inputPaths = (
+			);
 			name = "[CP] Copy Pods Resources";
 			outputFileListPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-NexilisLite/Pods-NexilisLite-resources-${CONFIGURATION}-output-files.xcfilelist",
 			);
+			outputPaths = (
+			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
 			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NexilisLite/Pods-NexilisLite-resources.sh\"\n";
@@ -1277,10 +1294,14 @@
 			inputFileListPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-NexilisLite-NexilisLiteTests/Pods-NexilisLite-NexilisLiteTests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
 			);
+			inputPaths = (
+			);
 			name = "[CP] Embed Pods Frameworks";
 			outputFileListPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-NexilisLite-NexilisLiteTests/Pods-NexilisLite-NexilisLiteTests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
 			);
+			outputPaths = (
+			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
 			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NexilisLite-NexilisLiteTests/Pods-NexilisLite-NexilisLiteTests-frameworks.sh\"\n";
@@ -1294,10 +1315,14 @@
 			inputFileListPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-NexilisLite-NexilisLiteTests/Pods-NexilisLite-NexilisLiteTests-resources-${CONFIGURATION}-input-files.xcfilelist",
 			);
+			inputPaths = (
+			);
 			name = "[CP] Copy Pods Resources";
 			outputFileListPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-NexilisLite-NexilisLiteTests/Pods-NexilisLite-NexilisLiteTests-resources-${CONFIGURATION}-output-files.xcfilelist",
 			);
+			outputPaths = (
+			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
 			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NexilisLite-NexilisLiteTests/Pods-NexilisLite-NexilisLiteTests-resources.sh\"\n";
@@ -1391,6 +1416,7 @@
 				CD1E72462A0BA86100BF871F /* GroupTopicViewController.swift in Sources */,
 				CD1E72472A0BA86100BF871F /* SettingTableViewController.swift in Sources */,
 				CD1E72172A0BA86100BF871F /* CategoryCC.swift in Sources */,
+				CD21CA5B2E0AA0C600DA6B77 /* CommunityModel.swift in Sources */,
 				CD1E72552A0BA86100BF871F /* BroadcastMembersTableViewController.swift in Sources */,
 				CD1E72152A0BA86100BF871F /* Chat.swift in Sources */,
 				CD1E72502A0BA86100BF871F /* BroadcastVariantViewController.swift in Sources */,
@@ -1398,6 +1424,7 @@
 				CD2B01242D43961E00C11B20 /* QRProfileController.swift in Sources */,
 				CD2B01252D43961E00C11B20 /* QRScannerController.swift in Sources */,
 				CD1E72252A0BA86100BF871F /* CustomTextView.swift in Sources */,
+				CD21CA592E0AA0A200DA6B77 /* SignInOption.swift in Sources */,
 				CD1E72602A0BA86100BF871F /* Callback.swift in Sources */,
 				CD1E724A2A0BA86100BF871F /* HistoryCCViewController.swift in Sources */,
 				CD1E722A2A0BA86100BF871F /* StreamingViewController.swift in Sources */,
@@ -1415,6 +1442,9 @@
 				CDDF467A2A2EF0A700049A19 /* ScreenSharingViewController.swift in Sources */,
 				CD1E72402A0BA86100BF871F /* ChangeNamePassswordViewController.swift in Sources */,
 				CD1E722E2A0BA86100BF871F /* AudioViewController.swift in Sources */,
+				CD21CA552E0AA07700DA6B77 /* CommunityNew.swift in Sources */,
+				CD21CA562E0AA07700DA6B77 /* ArchivedChatView.swift in Sources */,
+				CD21CA572E0AA07700DA6B77 /* CommunityList.swift in Sources */,
 				CD1E725E2A0BA86100BF871F /* TMessage.swift in Sources */,
 				CD1E72422A0BA86100BF871F /* ChangeDeviceViewController.swift in Sources */,
 				CD1E725B2A0BA86100BF871F /* AudienceViewController.swift in Sources */,

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

@@ -1722,7 +1722,7 @@ public class APIS: NSObject {
 //            Nexilis.getFeatureAccessWithKey(key: ["secure_folder_encrypt_key", "secure_folder_encrypt_iv", "secure_folder_offline"])
             Nexilis.getFeatureAccess()
         }
-        if FloatingButton.datePull == nil || !afterEnterBackground {
+        if (FloatingButton.datePull == nil || !afterEnterBackground) && Utils.getSetProfile() {
             DispatchQueue.global().async {
                 while API.nGetCLXConnState() == 0 || User.getMyPin() == nil {
                     Thread.sleep(forTimeInterval: 0.5)
@@ -2361,23 +2361,35 @@ public class APIS: NSObject {
         return (countMethod,typeMethod)
     }
     
-    public static func getControllerSign() -> UIViewController? {
+    public static func getControllerSign(forceSignIn: Bool = false) -> UIViewController? {
         let data = APIS.checkSignMethod()
         let count = data.0
         let type = data.1
         if count > 0 {
             var controller: UIViewController!
             if count == 1 {
-                let vc = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "signupsignin") as! SignUpSignIn
-                if type == 0 {
-                    vc.isMSISDN = true
+                if forceSignIn {
+                    let vc = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "changeDevice") as! ChangeDeviceViewController
+                    if type == 0 {
+                        vc.isMSISDN = true
+                        controller = vc
+                    } else if type == 1 {
+                        vc.isEmail = true
+                    }
+                    controller = vc
+                } else {
+                    let vc = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "signupsignin") as! SignUpSignIn
+                    if type == 0 {
+                        vc.isMSISDN = true
+                        controller = vc
+                    } else if type == 1 {
+                        vc.isEmail = true
+                    }
                     controller = vc
-                } else if type == 1 {
-                    vc.isEmail = true
                 }
-                controller = vc
             } else {
                 let vc = SignInOption()
+                vc.forceSignIn = forceSignIn
                 controller = vc
             }
             return controller

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

@@ -35,7 +35,7 @@ class Callback : CallBack {
     }
     
     func callStateChanged(nState: Int!, sMessage: String!) -> Int {
-        //print(nState,"/",sMessage)
+        print(nState,"/",sMessage)
         if nState == Nexilis.AUDIO_CALL_INCOMING || nState == Nexilis.VIDEO_CALL_INCOMING {
             if let delegate = Nexilis.shared.callDelegate {
                 if !Nexilis.showLibraryNotification || Nexilis.callAPNActivated {

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

@@ -1434,6 +1434,10 @@ public class CoreMessage_TMessageBank {
         tmessage.mBodies[CoreMessage_TMessageKey.EMAIL] = p_email
         tmessage.mBodies[CoreMessage_TMessageKey.OTP] = p_vercode
         tmessage.mBodies[CoreMessage_TMessageKey.PHONE_NUMBER] = number
+        tmessage.mBodies[CoreMessage_TMessageKey.ANDROID_ID] = Utils.M_USER_ANDROID_ID
+        tmessage.mBodies[CoreMessage_TMessageKey.ANDROID_APP_NAME] = APIS.getAppNm()
+        tmessage.mBodies[CoreMessage_TMessageKey.CPAAS_VERSION] = Utils.CPAAS_VERSION
+        tmessage.mBodies[CoreMessage_TMessageKey.ANDROID_PACKAGE_NAME] = (Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String) ?? ""
         if !deviceFingerprint.isEmpty {
             tmessage.mBodies[CoreMessage_TMessageKey.FINGERPRINT] = deviceFingerprint
         }

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

@@ -1047,7 +1047,7 @@ extension String {
                     if lower >= 0 {
                         text.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: lower, length: 1))
                         text.addAttribute(.foregroundColor, value: UIColor.mentionColor, range: NSRange(location: lower + 1, length: mention.fullName.count))
-                        text.addAttribute(.font, value: UIFont.systemFont(ofSize: 12 + String.offset(), weight: .medium), range: NSRange(location: lower, length: mention.fullName.count))
+                        text.addAttribute(.font, value: UIFont.systemFont(ofSize: 12 + String.offset(), weight: .medium), range: NSRange(location: lower, length: mention.fullName.count + 1))
                     }
                 }
             } else {

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

@@ -7,6 +7,7 @@
 
 import CryptoKit
 import LocalAuthentication
+import UIKit
 
 
 public class MasterKeyUtil {

+ 139 - 4
NexilisLite/NexilisLite/Source/Nexilis.swift

@@ -19,7 +19,7 @@ import CryptoKit
 import WebKit
 
 public class Nexilis: NSObject {
-    public static var cpaasVersion = "5.0.45"
+    public static var cpaasVersion = "5.0.46"
     public static var sAPIKey = ""
     
     public static var ADDRESS = ""
@@ -281,6 +281,7 @@ public class Nexilis: NSObject {
                         })
                     } else if isShowForceSignIn && !Utils.getForceAnonymous() && !Utils.getSetProfile() {
                         DispatchQueue.main.async {
+                            print("MASUK showForceSignIn")
                             showForceSignIn()
                         }
                     }
@@ -650,7 +651,7 @@ public class Nexilis: NSObject {
             if jsonArray[key] != nil {
                 return jsonArray[key] as! String == "1"
             } else {
-                if key == "sms" || key == "email" || key == "whatsapp" || key == "battery_optimization_force" || key == "backup_restore" || key == "check_sim_swap" || key == "admin_features" || key == "can_config_fb" || key == "friend_request_approval" || key == "authentication" || key == "sign_in_up_email" {
+                if key == "sms" || key == "email" || key == "whatsapp" || key == "battery_optimization_force" || key == "backup_restore" || key == "check_sim_swap" || key == "admin_features" || key == "can_config_fb" || key == "friend_request_approval" || key == "authentication" || key == "sign_in_up_msisdn" || key == "sign_in_up_email" {
                     return false
                 } else {
                     return true
@@ -712,8 +713,12 @@ public class Nexilis: NSObject {
     }
     
     public static func showForceSignIn(completion: (() -> Void)? = nil) {
-        let controller = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "changeDevice") as! ChangeDeviceViewController
-        controller.forceLogin = true
+        guard let controller = APIS.getControllerSign(forceSignIn: true) else { return }
+        if let controller = controller as? ChangeDeviceViewController {
+            controller.forceLogin = true
+        } else if let controller = controller as? SignInOption {
+            controller.forceLogin = true
+        }
         let navigationController = CustomNavigationController(rootViewController: controller)
         navigationController.modalPresentationStyle = .fullScreen
         navigationController.navigationBar.tintColor = .white
@@ -1502,6 +1507,7 @@ public class Nexilis: NSObject {
                 })
                 if !messageExist {
                     Nexilis.saveMessageBot(textMessage: "*\(nameReq.trimmingCharacters(in: .whitespaces))*" + "~" + "has requested to be your friend", blog_id: nameFpin, attachment_type: "61")
+                    self.makeNotifRequestFriend(message: message)
                     NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
                 }
             }
@@ -1673,6 +1679,135 @@ public class Nexilis: NSObject {
         
     }
     
+    private static func makeNotifRequestFriend(message: TMessage) {
+        let nameReq = message.getBody(key: CoreMessage_TMessageKey.MESSAGE_TEXT)
+        let profile = message.getBody(key: CoreMessage_TMessageKey.THUMB_ID)
+        print("HEHE: \(message.toLogString())")
+        DispatchQueue.main.async {
+            let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
+            let inEditorPersonal: String? = SecureUserDefaults.shared.value(forKey: "inEditorPersonal") ?? nil
+            if !onGoingCC.isEmpty {
+                return
+            }
+            if inEditorPersonal == "-999"{
+                return
+            }
+            let container = UIView()
+            container.backgroundColor = .gray
+            let profileImage = UIImageView()
+            profileImage.frame.size = CGSize(width: 60, height: 60)
+            container.addSubview(profileImage)
+            profileImage.translatesAutoresizingMaskIntoConstraints = false
+            NSLayoutConstraint.activate([
+                profileImage.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 8.0),
+                profileImage.centerYAnchor.constraint(equalTo: container.centerYAnchor),
+                profileImage.widthAnchor.constraint(equalToConstant: 60),
+                profileImage.heightAnchor.constraint(equalToConstant: 60),
+            ])
+            
+            let title = UILabel()
+            container.addSubview(title)
+            title.translatesAutoresizingMaskIntoConstraints = false
+            NSLayoutConstraint.activate([
+                title.leadingAnchor.constraint(equalTo: profileImage.trailingAnchor, constant: 8.0),
+                title.centerYAnchor.constraint(equalTo: container.centerYAnchor),
+                title.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -8.0)
+            ])
+            title.font = UIFont.systemFont(ofSize: 14)
+            title.text = nameReq.trimmingCharacters(in: .whitespaces) + " " + "has requested to be your friend".localized()
+            title.textColor = .white
+            title.numberOfLines = 0
+            
+            if Nexilis.shared.floating != nil {
+                Nexilis.shared.floating.dismiss()
+            }
+            Nexilis.shared.floating = FloatingNotificationBanner(customView: container)
+            Nexilis.shared.floating.bannerHeight = UIScreen.main.bounds.height / 6 - 10
+            Nexilis.shared.floating.transparency = 0.9
+            Nexilis.shared.floating.onTap = {
+                let editorPersonalVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorPersonalVC") as! EditorPersonal
+                editorPersonalVC.hidesBottomBarWhenPushed = true
+                editorPersonalVC.unique_l_pin = "-999"
+                editorPersonalVC.fromNotification = true
+                let navigationController = CustomNavigationController(rootViewController: editorPersonalVC)
+                navigationController.modalPresentationStyle = .fullScreen
+                navigationController.navigationBar.tintColor = .white
+                navigationController.navigationBar.barTintColor = UIApplication.shared.visibleViewController?.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
+                navigationController.navigationBar.isTranslucent = false
+                navigationController.navigationBar.overrideUserInterfaceStyle = .dark
+                navigationController.navigationBar.barStyle = .black
+                let cancelButtonAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
+                UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes, for: .normal)
+                let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
+                navigationController.navigationBar.titleTextAttributes = textAttributes
+                if UIApplication.shared.visibleViewController is UINavigationController && Nexilis.fromMAB {
+                    editorPersonalVC.fromNotification = false
+                    UIApplication.shared.visibleViewController?.show(editorPersonalVC, sender: nil)
+                } else {
+                    UIApplication.shared.visibleViewController?.present(navigationController, animated: true, completion: nil)
+                }
+            }
+            
+            if profile != "" {
+                profileImage.circle()
+                do {
+                    let documentDir = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
+                    let file = documentDir.appendingPathComponent(profile)
+                    if FileManager().fileExists(atPath: file.path) {
+                        profileImage.image = UIImage(contentsOfFile: file.path)
+                        profileImage.backgroundColor = .clear
+                    } else if FileEncryption.shared.isSecureExists(filename: profile) {
+                        do {
+                            if var data = try FileEncryption.shared.readSecure(filename: profile) {
+                                let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
+                                if dataDecrypt != nil {
+                                    data = dataDecrypt!
+                                }
+                                profileImage.image = UIImage(data: data)
+                                profileImage.backgroundColor = .clear
+                            }
+                        } catch {
+                            
+                        }
+                    } else {
+                        Download().startHTTP(forKey: profile) { (name, progress) in
+                            guard progress == 100 else {
+                                return
+                            }
+                            
+                            DispatchQueue.main.async {
+                                if FileEncryption.shared.isSecureExists(filename: profile) {
+                                    do {
+                                        if var data = try FileEncryption.shared.readSecure(filename: profile) {
+                                            let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
+                                            if dataDecrypt != nil {
+                                                data = dataDecrypt!
+                                            }
+                                            profileImage.image = UIImage(data: data)
+                                            profileImage.backgroundColor = .clear
+                                        }
+                                    } catch {
+                                        
+                                    }
+                                }
+                                Nexilis.shared.floating.show(queuePosition: .front, bannerPosition: .top, queue: NotificationBannerQueue(maxBannersOnScreenSimultaneously: 1), on: nil, edgeInsets: UIEdgeInsets(top: 8.0, left: 8.0, bottom: 0, right: 8.0), cornerRadius: 8.0, shadowColor: .clear, shadowOpacity: .zero, shadowBlurRadius: .zero, shadowCornerRadius: .zero, shadowOffset: .zero, shadowEdgeInsets: nil)
+                                return
+                            }
+                        }
+                    }
+                } catch {}
+                profileImage.contentMode = .scaleAspectFill
+            } else {
+                profileImage.circle()
+                profileImage.image = UIImage(systemName: "person")
+                profileImage.contentMode = .scaleAspectFit
+                profileImage.backgroundColor = .lightGray
+                profileImage.tintColor = .white
+            }
+            Nexilis.shared.floating.show(queuePosition: .front, bannerPosition: .top, queue: NotificationBannerQueue(maxBannersOnScreenSimultaneously: 1), on: nil, edgeInsets: UIEdgeInsets(top: 8.0, left: 8.0, bottom: 0, right: 8.0), cornerRadius: 8.0, shadowColor: .clear, shadowOpacity: .zero, shadowBlurRadius: .zero, shadowCornerRadius: .zero, shadowOffset: .zero, shadowEdgeInsets: nil)
+        }
+    }
+    
     public static func saveMessageBot(textMessage: String, blog_id: String, attachment_type:String)->Void{
         guard let me = User.getMyPin() else {
             return

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

@@ -1054,7 +1054,7 @@ class QmeraAudioViewController: UIViewController {
             else if state == Nexilis.AUDIO_VIDEO_CALL_MUTED {
                 DispatchQueue.main.async { [self] in
                     if let pin = arrayMessage.first, let index = users.firstIndex(of: User(pin: String(pin))) {
-                        if arrayMessage[1] == "1" {
+                        if (arrayMessage.count == 2 && arrayMessage[1] == "1") ||  (arrayMessage.count > 2 && arrayMessage[2] == "1") {
                             users[index].isMuted = true
                             if let profile = profiles.subviews[index] as? ProfileView {
                                 profile.imageMuted.isHidden = false

+ 5 - 1
NexilisLite/NexilisLite/Source/View/Call/QmeraVideoViewController.swift

@@ -1276,7 +1276,11 @@ class QmeraVideoViewController: UIViewController {
         else if state == Nexilis.AUDIO_VIDEO_CALL_MUTED {
             DispatchQueue.main.async { [self] in
                 if self.dataPerson.count == 1 {
-                    if arrayMessage[1] == "1" {
+                    var param = arrayMessage[1]
+                    if arrayMessage[2] != "." {
+                        param = arrayMessage[2]
+                    }
+                    if param == "1" {
                         mutedZoom.isHidden = false
                     } else {
                         mutedZoom.isHidden = true

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

@@ -93,6 +93,7 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
     var isAlwaysHideLinkPreview = false
     var timerCheckLink: Timer?
     var lastPositionCursorMention = 0
+    var lastTextLength = 0
     var timerFakeProgress: Timer?
     var showMenuContext = false
     var touchedSubview = UIView()
@@ -153,7 +154,17 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
         if (self.dataTopic["chat_id"]  as? String ?? "" != "") {
             l_pin = self.dataTopic["chat_id"]  as? String ?? ""
         }
-        let data: [String: String] = ["text": self.textFieldSend.textColor != UIColor.lightGray ? self.textFieldSend.text! : "", "reffId": self.reffId ?? ""]
+        var data: [String: Any] = ["text": self.textFieldSend.textColor != UIColor.lightGray ? self.textFieldSend.text! : "", "reffId": self.reffId ?? ""]
+        if listMentionInTextField.count > 0 {
+            var dataMention: [[String: String]] = []
+            for list in listMentionInTextField {
+                var dataTemp: [String: String] = [:]
+                dataTemp["f_pin_mention"] = list.pin
+                dataTemp["upper"] = list.ex_block
+                dataMention.append(dataTemp)
+            }
+            data["list_mention"] = dataMention
+        }
         if let jsonData = try? JSONSerialization.data(withJSONObject: data, options: []),
            let jsonString = String(data: jsonData, encoding: .utf8) {
             SecureUserDefaults.shared.set(jsonString, forKey: "new_saved_\(l_pin)")
@@ -517,14 +528,31 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
             if let dataSaved: String = SecureUserDefaults.shared.value(forKey: "new_saved_\(l_pin)") {
                 let data = dataSaved
                 if let jsonData = data.data(using: .utf8),
-                   let dataJson = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: String] {
-                    let last_m = dataJson["text"] ?? ""
-                    let last_r = dataJson["reffId"] ?? ""
+                   let dataJson = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] {
+                    let last_m = dataJson["text"] as? String ?? ""
+                    let last_r = dataJson["reffId"] as? String ?? ""
+                    let list_m = dataJson["list_mention"] as? [[String: String]] ?? []
+                    
                     if !last_m.isEmpty {
-                        textFieldSend.attributedText = last_m.richText(isEditing: true, group_id: self.dataGroup["group_id"]  as? String ?? "", listMentionInTextField: listMentionInTextField)
                         textFieldSend.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : UIColor.black
                     }
                     
+                    if list_m.count > 0 {
+                        for list in list_m {
+                            let f_pin = list["f_pin_mention"] ?? ""
+                            let upper = list["upper"] ?? ""
+                            let userFromBuddy = User.getData(pin: f_pin, lPin: l_pin)
+                            if userFromBuddy != nil {
+                                userFromBuddy!.ex_block = upper
+                                listMentionInTextField.append(userFromBuddy!)
+                            }
+                        }
+                    }
+                    
+                    if !last_m.isEmpty {
+                        textFieldSend.attributedText = last_m.richText(isEditing: true, group_id: self.dataGroup["group_id"]  as? String ?? "", listMentionInTextField: listMentionInTextField)
+                    }
+                    
                     if !last_r.isEmpty {
                         handleReply(indexPath: IndexPath(row: 0, section: 0), reffId: last_r)
                     }
@@ -1695,7 +1723,6 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
                 }
                 self.keyboardHeightForMention = keyboardHeight
                 if isSearching {
-                    self.constraintViewTextField.constant = self.constraintViewTextField.constant + 60
                     self.constraintBottomContainerMultpileSelectSession.constant = -keyboardHeight
                 }
                 UIView.animate(withDuration: TimeInterval(duration), animations: {
@@ -1910,6 +1937,7 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
         row[TypeDataMessage.opposite_pin] = opposite_pin
         row[TypeDataMessage.spec_file] = specFileString
         specFileString = ""
+        lastTextLength = 0
         if !dataDates.contains("Today".localized()){
             dataDates.append("Today".localized())
             tableChatView.insertSections(IndexSet(integer: dataDates.count - 1), with: .fade)
@@ -2763,22 +2791,30 @@ extension EditorGroup: UITextViewDelegate, CustomTextViewPasteDelegate {
             self.checkLink(fullText: textView.text)
         })
         
+        //indention code:
+        let text = textView.text ?? ""
+        let cursorPosition = textView.selectedRange.location
+        
+        let tempListMention = listMentionInTextField
         if listMentionInTextField.count > 0 {
             for j in 0..<listMentionInTextField.count {
-                if j > listMentionInTextField.count - 1{
-                    break
+                var index = j
+                if tempListMention.count != listMentionInTextField.count {
+                    index = j - (tempListMention.count - listMentionInTextField.count)
                 }
-                let name = (listMentionInTextField[j].firstName + " " + listMentionInTextField[j].lastName).trimmingCharacters(in: .whitespaces)
-                if !textView.text.contains("@\(name)") {
-                    listMentionInTextField.remove(at: j)
+                var upper = (Int(listMentionInTextField[index].ex_block ?? "0") ?? 0)
+                if cursorPosition <= upper {
+                    upper += text.count - lastTextLength
+                    listMentionInTextField[index].ex_block = "\(upper)"
+                }
+                let lower = upper - listMentionInTextField[index].fullName.count
+                let name = listMentionInTextField[index].fullName.trimmingCharacters(in: .whitespaces)
+                if textView.text.substring(from: lower, to: upper) != "@\(name)" {
+                    listMentionInTextField.remove(at: index)
                 }
             }
         }
         
-        //indention code:
-        let text = textView.text ?? ""
-        let cursorPosition = textView.selectedRange.location
-        
         // Handle Bullets (- [space] + letter → • )
         let bulletPattern = #"(?<=\n|^)- (\S)"#
         if let match = text.range(of: bulletPattern, options: .regularExpression) {
@@ -2810,6 +2846,7 @@ extension EditorGroup: UITextViewDelegate, CustomTextViewPasteDelegate {
         }
 
         handleRichText(textView)
+        lastTextLength = text.count
     }
     
     private func showMention(text: String) {
@@ -3111,7 +3148,6 @@ extension EditorGroup: UITextViewDelegate, CustomTextViewPasteDelegate {
                            let endIndex = text.index(startIndex, offsetBy: rangeReplacement.length, limitedBy: text.endIndex) {
                             text.replaceSubrange(startIndex..<endIndex, with: replacementText)
                         }
-                        
                         listMentionInTextField.remove(at: i)
                         
                         textView.attributedText = text.richText(isEditing: true, group_id: self.dataGroup["group_id"]  as? String ?? "", listMentionInTextField: listMentionInTextField)
@@ -3564,7 +3600,7 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
             forward.title = "Forward All".localized()
             delete.title = "Delete All".localized()
             children = [delete]
-            if Nexilis.checkingAccess(key: "secure_folder_forward") || (dataMessages[indexPath!.row][TypeDataMessage.spec_file] as? String ?? "").contains("forward") {
+            if (Nexilis.checkingAccess(key: "secure_folder_forward") || (dataMessages[indexPath!.row][TypeDataMessage.spec_file] as? String ?? "").contains("forward")) && dataMessages[indexPath!.row]["read_receipts"] as? String != "8" {
                 children.insert(forward, at: 0)
             }
         } else {
@@ -3576,7 +3612,7 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
             } else if dataMessages[indexPath!.row]["attachment_flag"]  as? String ?? "" == "11" {
                 children = [reply, delete]
             }
-            if (Nexilis.checkingAccess(key: "secure_folder_forward") && dataMessages[indexPath!.row]["attachment_flag"]  as? String ?? "" != "11") || (!(dataMessages[indexPath!.row][TypeDataMessage.message_text]  as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["image_id"]  as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["video_id"]  as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["file_id"]  as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["audio_id"]  as? String ?? "").isEmpty) || (dataMessages[indexPath!.row][TypeDataMessage.spec_file] as? String ?? "").contains("forward") {
+            if (Nexilis.checkingAccess(key: "secure_folder_forward") && dataMessages[indexPath!.row]["attachment_flag"]  as? String ?? "" != "11") || (!(dataMessages[indexPath!.row][TypeDataMessage.message_text]  as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["image_id"]  as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["video_id"]  as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["file_id"]  as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["audio_id"]  as? String ?? "").isEmpty && dataMessages[indexPath!.row]["read_receipts"] as? String != "8") || (dataMessages[indexPath!.row][TypeDataMessage.spec_file] as? String ?? "").contains("forward") {
                 children.insert(forward, at: 2)
             }
             if (dataMessages[indexPath!.row]["f_pin"]  as? String ?? "") == idMe {
@@ -3650,6 +3686,7 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
                                 listMentionWithText.append(fixUser!)
                                 listMentionInTextField.append(fixUser!)
                                 oldTextForTextview = oldTextForTextview.replacingOccurrences(of: result, with: "@\(fixUser!.fullName)")
+                                lastTextLength = oldTextForTextview.count
                             }
                             cursor.close()
                         }
@@ -3768,6 +3805,7 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
                 self.isEditingMessage = false
                 self.listMentionWithText = self.tempListMentionWithText
                 self.listMentionInTextField = self.tempListMentionWithText
+                self.lastTextLength = self.textFieldSend.text?.count ?? 0
                 self.heightTableEditMention = nil
                 self.editVC.dismiss(animated: true)
              })
@@ -3833,10 +3871,11 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
     
     @objc func dismissEditVC(_ sender: ObjectGesture) {
         if editTextView.text == sender.message_id {
-            self.isEditingMessage = false
-            self.listMentionWithText = self.tempListMentionWithText
-            self.listMentionInTextField = self.tempListMentionWithText
-            self.heightTableEditMention = nil
+            isEditingMessage = false
+            listMentionWithText = tempListMentionWithText
+            listMentionInTextField = tempListMentionWithText
+            lastTextLength = textFieldSend.text?.count ?? 0
+            heightTableEditMention = nil
             editVC.dismiss(animated: true)
         } else if self.isEditingMessage {
             let alert = LibAlertController(title: "".localized(), message: "Discard edit?".localized(), preferredStyle: .alert)
@@ -3845,11 +3884,13 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
                 self.isEditingMessage = false
                 self.listMentionWithText = self.tempListMentionWithText
                 self.listMentionInTextField = self.tempListMentionWithText
+                self.lastTextLength = self.textFieldSend.text?.count ?? 0
                 self.heightTableEditMention = nil
                 self.editVC.dismiss(animated: true)
             }))
             editVC.present(alert, animated: true, completion: nil)
         } else {
+            lastTextLength = self.textFieldSend.text?.count ?? 0
             editVC.dismiss(animated: true)
         }
     }
@@ -4589,6 +4630,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                         nowTextField.selectedTextRange = nowTextField.textRange(from: newPosition!, to: newPosition!)
                         
                         hideMention()
+                        lastTextLength = nowTextField.text.count
                         return
                     }
                 }
@@ -4755,7 +4797,11 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
         timeMessage.numberOfLines = 0
         cellMessage.contentView.addSubview(timeMessage)
         timeMessage.translatesAutoresizingMaskIntoConstraints = false
-        if (dataMessages[indexPath.row]["read_receipts"] as? String) == "8" || ((dataMessages[indexPath.row]["credential"] as? String) == "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
+        if ((dataMessages[indexPath.row]["read_receipts"] as? String) == "8" ||
+            (dataMessages[indexPath.row]["credential"] as? String) == "1" ||
+            !(dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String ?? "").isEmpty) &&
+            (dataMessages[indexPath.row]["lock"] as? String) != "2" &&
+            (dataMessages[indexPath.row]["lock"] as? String) != "1" {
             timeMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -40).isActive = true
         } else {
             timeMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
@@ -4855,11 +4901,6 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
             
             containerMessage.topAnchor.constraint(equalTo: cellMessage.contentView.topAnchor, constant: 5).isActive = true
             containerMessage.leadingAnchor.constraint(greaterThanOrEqualTo: cellMessage.contentView.leadingAnchor, constant: 60).isActive = true
-            if (dataMessages[indexPath.row]["read_receipts"] as? String) == "8" || ((dataMessages[indexPath.row]["credential"] as? String) == "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
-                containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -40).isActive = true
-            } else {
-                containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
-            }
             containerMessage.trailingAnchor.constraint(equalTo: profileMessage.leadingAnchor, constant: -5).isActive = true
             containerMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
             containerMessage.layer.cornerRadius = 10.0
@@ -4978,11 +5019,6 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
             }
             
             containerMessage.leadingAnchor.constraint(equalTo: profileMessage.trailingAnchor, constant: 5).isActive = true
-            if (dataMessages[indexPath.row]["read_receipts"] as? String) == "8" || ((dataMessages[indexPath.row]["credential"] as? String) == "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
-                containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -40).isActive = true
-            } else {
-                containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
-            }
             containerMessage.trailingAnchor.constraint(lessThanOrEqualTo: cellMessage.contentView.trailingAnchor, constant: -60).isActive = true
             containerMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
             if dataMessages[indexPath.row]["attachment_flag"] as? String == "11" && dataMessages[indexPath.row]["reff_id"]as? String == "" && dataMessages[indexPath.row]["lock"]  as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2" {
@@ -5013,6 +5049,16 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
             nameSender.textColor = .mainColor
         }
         
+        if ((dataMessages[indexPath.row]["read_receipts"] as? String) == "8" ||
+            (dataMessages[indexPath.row]["credential"] as? String) == "1" ||
+            !(dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String ?? "").isEmpty) &&
+            (dataMessages[indexPath.row]["lock"] as? String) != "2" &&
+            (dataMessages[indexPath.row]["lock"] as? String) != "1" {
+            containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -40).isActive = true
+        } else {
+            containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
+        }
+        
         let imageStared = UIImageView()
         let imageAckView = UIImageView()
         let imageCredentialView = UIImageView()
@@ -5033,7 +5079,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
             imageStared.tintColor = .systemYellow
         }
         
-        if dataMessages[indexPath.row]["read_receipts"] as? String == "8" {
+        if dataMessages[indexPath.row]["read_receipts"] as? String == "8"  && (dataMessages[indexPath.row]["lock"] as? String) != "2" && (dataMessages[indexPath.row]["lock"] as? String) != "1" {
             var imageAck = UIImage(named: "ack_icon_gray", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
             cellMessage.contentView.addSubview(imageAckView)
             imageAckView.translatesAutoresizingMaskIntoConstraints = false

+ 18 - 15
NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift

@@ -2624,7 +2624,6 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
 //                self.constraintViewTextField.constant = keyboardHeight - 60
                 self.constraintBottomAttachment.constant = keyboardHeight
                 if isSearching {
-                    self.constraintViewTextField.constant = self.constraintViewTextField.constant + 60
                     self.constraintBottomContainerMultpileSelectSession.constant = -keyboardHeight
                 }
                 UIView.animate(withDuration: TimeInterval(duration), animations: {
@@ -4787,7 +4786,7 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
             forward.title = "Forward All".localized()
             delete.title = "Delete All".localized()
             children = [delete]
-            if Nexilis.checkingAccess(key: "secure_folder_forward") || (dataMessages[indexPath!.row][TypeDataMessage.spec_file] as? String ?? "").contains("forward") {
+            if (Nexilis.checkingAccess(key: "secure_folder_forward") || (dataMessages[indexPath!.row][TypeDataMessage.spec_file] as? String ?? "").contains("forward")) && dataMessages[indexPath!.row]["read_receipts"] as? String != "8" {
                 children.insert(forward, at: 0)
             }
         } else {
@@ -4796,7 +4795,7 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
             } else if dataMessages[indexPath!.row]["attachment_flag"]  as? String ?? "" == "11" {
                children = [reply, delete]
             }
-            if (Nexilis.checkingAccess(key: "secure_folder_forward") && dataMessages[indexPath!.row]["attachment_flag"]  as? String ?? "" != "11") || (!(dataMessages[indexPath!.row][TypeDataMessage.message_text]  as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["image_id"]  as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["video_id"]  as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["file_id"]  as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["audio_id"]  as? String ?? "").isEmpty) || (dataMessages[indexPath!.row][TypeDataMessage.spec_file] as? String ?? "").contains("forward") {
+            if (Nexilis.checkingAccess(key: "secure_folder_forward") && dataMessages[indexPath!.row]["attachment_flag"]  as? String ?? "" != "11") || (!(dataMessages[indexPath!.row][TypeDataMessage.message_text]  as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["image_id"]  as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["video_id"]  as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["file_id"]  as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["audio_id"]  as? String ?? "").isEmpty && dataMessages[indexPath!.row]["read_receipts"] as? String != "8") || (dataMessages[indexPath!.row][TypeDataMessage.spec_file] as? String ?? "").contains("forward") {
                 children.insert(forward, at: 2)
             }
             if (dataMessages[indexPath!.row]["f_pin"]  as? String ?? "") == idMe {
@@ -6098,7 +6097,11 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
         timeMessage.numberOfLines = 0
         cell.contentView.addSubview(timeMessage)
         timeMessage.translatesAutoresizingMaskIntoConstraints = false
-        if (dataMessages[indexPath.row]["read_receipts"] as? String) == "8" || ((dataMessages[indexPath.row]["credential"] as? String) == "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
+        if ((dataMessages[indexPath.row]["read_receipts"] as? String) == "8" ||
+            (dataMessages[indexPath.row]["credential"] as? String) == "1" ||
+            !(dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String ?? "").isEmpty) &&
+            (dataMessages[indexPath.row]["lock"] as? String) != "2" &&
+            (dataMessages[indexPath.row]["lock"] as? String) != "1" {
             timeMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -40).isActive = true
         } else {
             timeMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -5).isActive = true
@@ -6162,11 +6165,6 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
         
         if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
             containerMessage.leadingAnchor.constraint(greaterThanOrEqualTo: cell.contentView.leadingAnchor, constant: 60).isActive = true
-            if (dataMessages[indexPath.row]["read_receipts"] as? String) == "8" || ((dataMessages[indexPath.row]["credential"] as? String) == "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
-                containerMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -40).isActive = true
-            } else {
-                containerMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -5).isActive = true
-            }
             if isContactCenter {
                 containerMessage.topAnchor.constraint(equalTo: nameSender.bottomAnchor).isActive = true
                 containerMessage.trailingAnchor.constraint(equalTo: profileMessage.leadingAnchor, constant: -5).isActive = true
@@ -6260,11 +6258,6 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                     containerMessage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 15).isActive = true
                 }
             }
-            if (dataMessages[indexPath.row]["read_receipts"] as? String) == "8" || ((dataMessages[indexPath.row]["credential"] as? String) == "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
-                containerMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -40).isActive = true
-            } else {
-                containerMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -5).isActive = true
-            }
             containerMessage.trailingAnchor.constraint(lessThanOrEqualTo: cell.contentView.trailingAnchor, constant: -60).isActive = true
             containerMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
             if dataMessages[indexPath.row]["attachment_flag"] as? String == "11" && dataMessages[indexPath.row]["reff_id"]as? String == "" && dataMessages[indexPath.row]["lock"]  as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2" {
@@ -6279,6 +6272,16 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
             timeMessage.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: 8).isActive = true
         }
         
+        if ((dataMessages[indexPath.row]["read_receipts"] as? String) == "8" ||
+            (dataMessages[indexPath.row]["credential"] as? String) == "1" ||
+            !(dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String ?? "").isEmpty) &&
+            (dataMessages[indexPath.row]["lock"] as? String) != "2" &&
+            (dataMessages[indexPath.row]["lock"] as? String) != "1" {
+            containerMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -40).isActive = true
+        } else {
+            containerMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -5).isActive = true
+        }
+        
         let imageStared = UIImageView()
         if dataMessages[indexPath.row]["is_stared"] as? String == "1" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"]  as? String ?? "" == "0") {
             cell.contentView.addSubview(imageStared)
@@ -6299,7 +6302,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
         
         let imageAckView = UIImageView()
         let imageCredentialView = UIImageView()
-        if dataMessages[indexPath.row]["read_receipts"] as? String == "8" {
+        if dataMessages[indexPath.row]["read_receipts"] as? String == "8" && (dataMessages[indexPath.row]["lock"] as? String) != "2" && (dataMessages[indexPath.row]["lock"] as? String) != "1" {
             var imageAck = UIImage(named: "ack_icon_gray", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
             if dataMessages[indexPath.row]["status"] as? String == "8" {
                 imageAck = UIImage(named: "ack_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)

+ 67 - 51
NexilisLite/NexilisLite/Source/View/Chat/EditorStarMessages.swift

@@ -171,7 +171,15 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
         let timeMessage = UILabel()
         cellMessage.contentView.addSubview(timeMessage)
         timeMessage.translatesAutoresizingMaskIntoConstraints = false
-        timeMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5 - 20).isActive = true
+        if ((dataMessages[indexPath.row]["read_receipts"] as? String) == "8" ||
+            (dataMessages[indexPath.row]["credential"] as? String) == "1" ||
+            !(dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String ?? "").isEmpty) &&
+            (dataMessages[indexPath.row]["lock"] as? String) != "2" &&
+            (dataMessages[indexPath.row]["lock"] as? String) != "1" {
+            timeMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -40).isActive = true
+        } else {
+            timeMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
+        }
         
         let messageText = UITextView()
         messageText.isEditable = false
@@ -210,7 +218,6 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
             
             containerMessage.topAnchor.constraint(equalTo: cellMessage.contentView.topAnchor, constant: 5).isActive = true
             containerMessage.leadingAnchor.constraint(greaterThanOrEqualTo: cellMessage.contentView.leadingAnchor, constant: 80).isActive = true
-            containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5 - 20).isActive = true
             containerMessage.trailingAnchor.constraint(equalTo: profileMessage.leadingAnchor, constant: -5).isActive = true
             containerMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
             if (dataMessages[indexPath.row]["attachment_flag"] as? String == "11" && dataMessages[indexPath.row]["reff_id"]as? String == "") {
@@ -286,7 +293,6 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
             
             containerMessage.topAnchor.constraint(equalTo: cellMessage.contentView.topAnchor, constant: 5).isActive = true
             containerMessage.leadingAnchor.constraint(equalTo: profileMessage.trailingAnchor, constant: 5).isActive = true
-            containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5 - 20).isActive = true
             containerMessage.trailingAnchor.constraint(lessThanOrEqualTo: cellMessage.contentView.trailingAnchor, constant: -80).isActive = true
             containerMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
             if (dataMessages[indexPath.row]["attachment_flag"] as? String == "11" && dataMessages[indexPath.row]["reff_id"]as? String == "") {
@@ -312,6 +318,16 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
             nameSender.textColor = .mainColor
         }
         
+        if ((dataMessages[indexPath.row]["read_receipts"] as? String) == "8" ||
+            (dataMessages[indexPath.row]["credential"] as? String) == "1" ||
+            !(dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String ?? "").isEmpty) &&
+            (dataMessages[indexPath.row]["lock"] as? String) != "2" &&
+            (dataMessages[indexPath.row]["lock"] as? String) != "1" {
+            containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -40).isActive = true
+        } else {
+            containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
+        }
+        
         if (dataMessages[indexPath.row]["is_stared"] as? String == "1") {
             let imageStared = UIImageView()
             cellMessage.contentView.addSubview(imageStared)
@@ -329,6 +345,53 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
             imageStared.backgroundColor = .clear
             imageStared.tintColor = .systemYellow
         }
+        
+        let imageAckView = UIImageView()
+        if dataMessages[indexPath.row]["read_receipts"] as? String == "8" {
+            var imageAck = UIImage(named: "ack_icon_gray", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
+            if dataMessages[indexPath.row]["status"] as? String == "8" {
+                imageAck = UIImage(named: "ack_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
+            }
+            imageAckView.image = imageAck
+            cellMessage.contentView.addSubview(imageAckView)
+            imageAckView.translatesAutoresizingMaskIntoConstraints = false
+            imageAckView.widthAnchor.constraint(equalToConstant: 30).isActive = true
+            imageAckView.heightAnchor.constraint(equalToConstant: 30).isActive = true
+            imageAckView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
+            if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
+                imageAckView.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 30).isActive = true
+            } else {
+                imageAckView.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -30).isActive = true
+                let tap = ObjectGesture(target: self, action: #selector(tapAck(_:)))
+                tap.indexPath = indexPath
+                imageAckView.addGestureRecognizer(tap)
+                imageAckView.isUserInteractionEnabled = true
+            }
+        }
+        
+        if !(dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String ?? "").isEmpty && (dataMessages[indexPath.row]["lock"] as? String) != "2" && (dataMessages[indexPath.row]["lock"] as? String) != "1" {
+            let imageSpecFileView = UIImageView()
+            let imageSpecFile = UIImage(named: "pb_ic_attach_spc", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
+            imageSpecFileView.image = imageSpecFile
+            cellMessage.contentView.addSubview(imageSpecFileView)
+            imageSpecFileView.translatesAutoresizingMaskIntoConstraints = false
+            imageSpecFileView.widthAnchor.constraint(equalToConstant: 30).isActive = true
+            imageSpecFileView.heightAnchor.constraint(equalToConstant: 30).isActive = true
+            imageSpecFileView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
+            if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
+                if imageAckView.isDescendant(of: cellMessage.contentView) {
+                    imageSpecFileView.leadingAnchor.constraint(equalTo: imageAckView.trailingAnchor, constant: 5).isActive = true
+                } else {
+                    imageSpecFileView.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 30).isActive = true
+                }
+            } else {
+                if imageAckView.isDescendant(of: cellMessage.contentView) {
+                    imageSpecFileView.trailingAnchor.constraint(equalTo: imageAckView.leadingAnchor, constant: -5).isActive = true
+                } else {
+                    imageSpecFileView.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -30).isActive = true
+                }
+            }
+        }
         topMarginText.isActive = true
         if dataMessages[indexPath.row]["attachment_flag"] as! String == "27" || dataMessages[indexPath.row]["attachment_flag"] as! String == "26" {
             messageText.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 85).isActive = true
@@ -842,53 +905,6 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
             }
         }
         
-        let imageAckView = UIImageView()
-        if dataMessages[indexPath.row]["read_receipts"] as? String == "8" {
-            var imageAck = UIImage(named: "ack_icon_gray", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
-            if dataMessages[indexPath.row]["status"] as? String == "8" {
-                imageAck = UIImage(named: "ack_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
-            }
-            imageAckView.image = imageAck
-            cellMessage.contentView.addSubview(imageAckView)
-            imageAckView.translatesAutoresizingMaskIntoConstraints = false
-            imageAckView.widthAnchor.constraint(equalToConstant: 30).isActive = true
-            imageAckView.heightAnchor.constraint(equalToConstant: 30).isActive = true
-            imageAckView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
-            if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
-                imageAckView.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 30).isActive = true
-            } else {
-                imageAckView.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -30).isActive = true
-                let tap = ObjectGesture(target: self, action: #selector(tapAck(_:)))
-                tap.indexPath = indexPath
-                imageAckView.addGestureRecognizer(tap)
-                imageAckView.isUserInteractionEnabled = true
-            }
-        }
-        
-        if !(dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String ?? "").isEmpty && (dataMessages[indexPath.row]["lock"] as? String) != "2" && (dataMessages[indexPath.row]["lock"] as? String) != "1" {
-            let imageSpecFileView = UIImageView()
-            let imageSpecFile = UIImage(named: "pb_ic_attach_spc", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
-            imageSpecFileView.image = imageSpecFile
-            cellMessage.contentView.addSubview(imageSpecFileView)
-            imageSpecFileView.translatesAutoresizingMaskIntoConstraints = false
-            imageSpecFileView.widthAnchor.constraint(equalToConstant: 30).isActive = true
-            imageSpecFileView.heightAnchor.constraint(equalToConstant: 30).isActive = true
-            imageSpecFileView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
-            if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
-                if imageAckView.isDescendant(of: cellMessage.contentView) {
-                    imageSpecFileView.leadingAnchor.constraint(equalTo: imageAckView.trailingAnchor, constant: 5).isActive = true
-                } else {
-                    imageSpecFileView.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 30).isActive = true
-                }
-            } else {
-                if imageAckView.isDescendant(of: cellMessage.contentView) {
-                    imageSpecFileView.trailingAnchor.constraint(equalTo: imageAckView.leadingAnchor, constant: -5).isActive = true
-                } else {
-                    imageSpecFileView.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -30).isActive = true
-                }
-            }
-        }
-        
         return cellMessage
     }
     
@@ -1590,7 +1606,7 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
         if self.dataMessages[indexPath!.row]["f_pin"] as! String == "-999" || !(dataMessages[indexPath!.row]["image_id"] as! String).isEmpty || !(dataMessages[indexPath!.row]["video_id"] as! String).isEmpty || !(dataMessages[indexPath!.row]["file_id"] as! String).isEmpty || dataMessages[indexPath!.row]["attachment_flag"] as! String == "11" {
             children = [star]
         }
-        if (Nexilis.checkingAccess(key: "secure_folder_forward") || (dataMessages[indexPath!.row][TypeDataMessage.spec_file] as? String ?? "").contains("forward")) && self.dataMessages[indexPath!.row]["f_pin"] as! String != "-999" {
+        if (Nexilis.checkingAccess(key: "secure_folder_forward") || (dataMessages[indexPath!.row][TypeDataMessage.spec_file] as? String ?? "").contains("forward")) && self.dataMessages[indexPath!.row]["f_pin"] as! String != "-999" && dataMessages[indexPath!.row]["read_receipts"] as? String != "8" {
             children.insert(forward, at: 1)
         }
         

+ 339 - 172
NexilisLite/NexilisLite/Source/View/Control/ChangeDeviceViewController.swift

@@ -8,6 +8,7 @@
 import UIKit
 import NotificationBannerSwift
 import nuSDKService
+import FirebaseAuth
 
 public class ChangeDeviceViewController: UIViewController {
     @IBOutlet weak var usernameField: UITextField!
@@ -17,11 +18,12 @@ public class ChangeDeviceViewController: UIViewController {
     
     public var isDismiss: ((String) -> ())?
     public var forceLogin = false
+    public var isEmail = false
+    public var isMSISDN = false
     
     public override func viewDidLoad() {
         super.viewDidLoad()
         
-//        self.view.backgroundColor = .white
         let attributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
         let navBarAppearance = UINavigationBarAppearance()
         navBarAppearance.configureWithOpaqueBackground()
@@ -31,14 +33,23 @@ public class ChangeDeviceViewController: UIViewController {
         navigationController?.navigationBar.scrollEdgeAppearance = navBarAppearance
         navigationController?.navigationBar.tintColor = .white
 
+        var textTitle = "Please enter your nickname and your password".localized()
+        var textPlaceHolder = "Your Nickname".localized()
+        if isEmail {
+            textTitle = "Please enter your registered email address.".localized()
+            textPlaceHolder = "Your Email".localized()
+        } else if isMSISDN {
+            textTitle = "Please enter your registered phone number.".localized()
+            textPlaceHolder = "Your Phone Number (082...)".localized()
+        }
         self.title = "Sign-In".localized()
-        descLogin.text = "Please enter your registered nickname or email address to Sign-In".localized()
+        descLogin.text = textTitle
         navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Submit".localized(), style: .plain, target: self, action: #selector(didTapSubmit(sender:)))
         
         passwordField.addPadding(.right(40))
         passwordField.isSecureTextEntry = true
         showPasswordButton.setImage(UIImage(systemName: "eye.slash.fill"), for: .normal)
-        usernameField.placeholder = "Your Nickname".localized() + "/" + "Email".localized()
+        usernameField.placeholder = textPlaceHolder
         passwordField.placeholder = "Password".localized()
         usernameField.addTarget(self, action: #selector(checkUsername(_:)), for: .editingChanged)
         
@@ -47,6 +58,13 @@ public class ChangeDeviceViewController: UIViewController {
         let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
         tapGesture.cancelsTouchesInView = false
         view.addGestureRecognizer(tapGesture)
+        if isEmail || isMSISDN {
+            passwordField.isHidden = true
+            showPasswordButton.isHidden = true
+            if isMSISDN{
+                usernameField.keyboardType = .numberPad
+            }
+        }
     }
     
     public override func viewWillAppear(_ animated: Bool) {
@@ -56,14 +74,14 @@ public class ChangeDeviceViewController: UIViewController {
     }
     
     @objc func checkUsername(_ textField: UITextField) {
-        let text : String! = usernameField.text
-        if isValidEmail(text) {
-            passwordField.isHidden = true
-            showPasswordButton.isHidden = true
-        } else if passwordField.isHidden {
-            passwordField.isHidden = false
-            showPasswordButton.isHidden = false
-        }
+//        let text : String! = usernameField.text
+//        if isValidEmail(text) {
+//            passwordField.isHidden = true
+//            showPasswordButton.isHidden = true
+//        } else if passwordField.isHidden {
+//            passwordField.isHidden = false
+//            showPasswordButton.isHidden = false
+//        }
     }
     
     func isValidEmail(_ email: String) -> Bool {
@@ -74,7 +92,6 @@ public class ChangeDeviceViewController: UIViewController {
     }
     
     @objc func dismissKeyboard() {
-        //Causes the view (or one of its embedded text fields) to resign the first responder status.
         view.endEditing(true)
     }
     
@@ -90,96 +107,217 @@ public class ChangeDeviceViewController: UIViewController {
     
     func checkEmail(email: String) {
         if !CheckConnection.isConnectedToNetwork() || API.nGetCLXConnState() == 0 {
-            let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
-            imageView.tintColor = .white
-            let banner = FloatingNotificationBanner(title: "Check your connection".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
-            banner.show()
+            self.showFailedSignUpIn(title: "Check your connection".localized(), withLoader: false)
             return
         }
         Nexilis.showLoader()
         DispatchQueue.global().async {
             if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.getSendOTPLogin(p_email: email), timeout: 30 * 1000) {
                 if response.getBody(key: CoreMessage_TMessageKey.ERRCOD, default_value: "99") != "00" {
+                    DispatchQueue.main.async {
+                        self.showFailedSignUpIn(title: "Unregistered email account".localized())
+                    }
+                } else {
                     DispatchQueue.main.async {
                         Nexilis.hideLoader(completion: {
-                            let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
-                            imageView.tintColor = .white
-                            let banner = FloatingNotificationBanner(title: "Unregistered email account".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
-                            banner.show()
+                            self.showPageOTP(email: email)
                         })
                     }
+                }
+            } else {
+                DispatchQueue.main.async {
+                    self.showFailedSignUpIn(title: "Unable to access servers. Try again later".localized())
+                }
+            }
+        }
+    }
+    
+    func checkNumber(number: String) {
+        var number = number
+        if number.hasPrefix("0") {
+            number = number.replacingCharacters(in: number.startIndex...number.startIndex, with: "+62")
+        }
+        if !CheckConnection.isConnectedToNetwork() || API.nGetCLXConnState() == 0 {
+            self.showFailedSignUpIn(title: "Check your connection".localized(), withLoader: false)
+            return
+        }
+        Nexilis.showLoader()
+        DispatchQueue.global().async {
+            if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getCheckMSISDN(number: number)) {
+                print("KUKU: \(response.toLogString())")
+                if response.getBody(key: CoreMessage_TMessageKey.ERRCOD, default_value: "99") != "00" {
+                    DispatchQueue.main.async {
+                        self.showFailedSignUpIn(title: "Unregistered phone number".localized())
+                    }
                 } else {
                     DispatchQueue.main.async {
                         Nexilis.hideLoader(completion: {
-                            self.showPageOTP(email: email)
+                            self.sendOTP(to: number)
                         })
                     }
                 }
             } else {
                 DispatchQueue.main.async {
-                    Nexilis.hideLoader(completion: {
-                        let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
-                        imageView.tintColor = .white
-                        let banner = FloatingNotificationBanner(title: "Unable to access servers. Try again later".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
-                        banner.show()
-                    })
+                    self.showFailedSignUpIn(title: "Unable to access servers. Try again later".localized())
+                }
+            }
+        }
+    }
+    
+    func sendOTP(to phoneNumber: String) {
+        PhoneAuthProvider.provider().verifyPhoneNumber(phoneNumber, uiDelegate: nil) { verificationID, error in
+            if let error = error {
+                UIApplication.shared.visibleViewController?.view.makeToast("Error sending OTP: \(error)".localized(), duration: 3, position: .center)
+                return
+            }
+
+            Utils.setUserMSISDN(value: verificationID ?? "")
+            self.showPageOTP(phone: phoneNumber)
+        }
+    }
+    
+    func verifyOTP(_ code: String, number: String, privateKey: SecKey) {
+        let verificationID = Utils.getUserMSISDN()
+        let credential = PhoneAuthProvider.provider().credential(
+            withVerificationID: verificationID,
+            verificationCode: code
+        )
+
+        Auth.auth().signIn(with: credential) { authResult, error in
+            if error != nil {
+                self.showFailedSignUpIn(title: "Invalid OTP".localized())
+                self.showPageOTP(phone: number)
+                return
+            }
+
+            DispatchQueue.global().async {
+                if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getChalanger()) {
+                    if response.isOk() {
+                        let data = response.getBody(key: CoreMessage_TMessageKey.DATA, default_value: "")
+                        if data.isEmpty {
+                            DispatchQueue.main.async {
+                                self.showFailedSignUpIn(title: "Failed to get auth, please try again".localized())
+                            }
+                            return
+                        }
+                        var pk = ""
+                        var sign = ""
+                        let df = HMACDeviceFingerprintNexilis.generate()
+                        if let dataSign = "\(data)!\(df)".data(using: .utf8) {
+                            if let signature = KeyManagerNexilis.sign(data: dataSign, privateKey: privateKey) {
+                                sign = signature.base64EncodedString()
+                            }
+                        }
+                        if let publicKey = KeyManagerNexilis.getRSAX509PublicKeyBase64(privateKey: privateKey) {
+                            pk = publicKey
+                        }
+                        if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.getSendVerifyChangeDevice(p_email: "", p_vercode: "", number: number, deviceFingerprint: df, publicKey: pk, signature: sign), timeout: 30 * 1000) {
+                            if !response.isOk() {
+                                DispatchQueue.main.async {
+                                    DispatchQueue.main.async {
+                                        self.showFailedSignUpIn(title: "Failed".localized())
+                                    }
+                                }
+                            } else {
+                                self.successSubmit(response: response, name: "", number: number)
+                            }
+                        } else {
+                            DispatchQueue.main.async {
+                                self.showFailedSignUpIn(title: "Unable to access servers. Try again later".localized())
+                            }
+                        }
+                    } else {
+                        DispatchQueue.main.async {
+                            self.showFailedSignUpIn(title: "Failed to get auth, please try again".localized())
+                        }
+                    }
+                } else {
+                    DispatchQueue.main.async {
+                        self.showFailedSignUpIn(title: "Unable to access servers. Try again later".localized())
+                    }
                 }
             }
         }
     }
     
-    func showPageOTP(email: String, errCode:String = "") {
+    func showPageOTP(email: String = "", phone: String = "", errCode:String = "") {
         let showOTPVC = VerifyEmail()
         showOTPVC.email = email
+        showOTPVC.msisdn = phone
+        showOTPVC.isMSISDN = !phone.isEmpty
         showOTPVC.showWrongOTP = errCode
         showOTPVC.isDismiss = { code in
+            if !CheckConnection.isConnectedToNetwork() || API.nGetCLXConnState() == 0 {
+                self.showFailedSignUpIn(title: "Check your connection".localized(), withLoader: false)
+                return
+            }
+            if KeyManagerNexilis.hasGeneratedKey() {
+                KeyManagerNexilis.deleteKey()
+                KeyManagerNexilis.deleteMarker()
+            }
+            KeyManagerNexilis.generateKey()
+            KeyManagerNexilis.saveMarker()
+            guard let privateKey = KeyManagerNexilis.getPrivateKey() else {
+                KeyManagerNexilis.deleteKey()
+                KeyManagerNexilis.deleteMarker()
+                UIApplication.shared.visibleViewController?.view.makeToast("Biometric or passcode authentication required".localized(), duration: 3, position: .center)
+                return
+            }
+            if Database.shared.openDatabase() == 0 {
+                APIS.showRestartApp()
+                KeyManagerNexilis.deleteKey()
+                KeyManagerNexilis.deleteMarker()
+                return
+            }
             Nexilis.showLoader()
+            if !phone.isEmpty {
+                self.verifyOTP(code, number: phone, privateKey: privateKey)
+                return
+            }
             DispatchQueue.global().async {
-                if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.getSendVerifyChangeDevice(p_email: email, p_vercode: code), timeout: 30 * 1000) {
-                    if !response.isOk() {
-                        DispatchQueue.main.async {
-                            Nexilis.hideLoader(completion: {
-                                self.showPageOTP(email: email, errCode: response.getBody(key: CoreMessage_TMessageKey.ERRCOD, default_value: "99"))
-                            })
+                if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getChalanger()) {
+                    if response.isOk() {
+                        let data = response.getBody(key: CoreMessage_TMessageKey.DATA, default_value: "")
+                        if data.isEmpty {
+                            DispatchQueue.main.async {
+                                self.showFailedSignUpIn(title: "Failed to get auth, please try again".localized())
+                            }
+                            return
                         }
-                    } else {
-                        self.deleteAllRecordDatabase()
-                        let id = response.getBody(key: CoreMessage_TMessageKey.F_PIN, default_value: "")
-                        let thumb = response.getBody(key: CoreMessage_TMessageKey.THUMB_ID, default_value: "")
-                        if(!id.isEmpty) {
-//                            Nexilis.changeUser(f_pin: id)
-                            Utils.setProfile(value: true)
-                            // pos registration
-                            _ = Nexilis.write(message: CoreMessage_TMessageBank.getPostRegistration(p_pin: id))
-                            DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
-                                Nexilis.hideLoader(completion: {
-                                    let imageView = UIImageView(image: UIImage(systemName: "checkmark.circle.fill"))
-                                    imageView.tintColor = .white
-                                    let banner = FloatingNotificationBanner(title: "Successfully Sign-In".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .success, colors: nil, iconPosition: .center)
-                                    banner.show()
-                                    if Nexilis.showFB {
-                                        Nexilis.floatingButton.removeFromSuperview()
-                                        Nexilis.floatingButton = FloatingButton()
-                                        Nexilis.addFB()
-                                    }
-                                    if !self.forceLogin {
-                                        self.navigationController?.popViewController(animated: true)
-                                    } else {
-                                        self.navigationController?.dismiss(animated: true)
+                        var pk = ""
+                        var sign = ""
+                        let df = HMACDeviceFingerprintNexilis.generate()
+                        if let dataSign = "\(data)!\(df)".data(using: .utf8) {
+                            if let signature = KeyManagerNexilis.sign(data: dataSign, privateKey: privateKey) {
+                                sign = signature.base64EncodedString()
+                            }
+                        }
+                        if let publicKey = KeyManagerNexilis.getRSAX509PublicKeyBase64(privateKey: privateKey) {
+                            pk = publicKey
+                        }
+                        if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.getSendVerifyChangeDevice(p_email: email, p_vercode: code, deviceFingerprint: df, publicKey: pk, signature: sign), timeout: 30 * 1000) {
+                            if !response.isOk() {
+                                DispatchQueue.main.async {
+                                    Nexilis.hideLoader {
+                                        self.showPageOTP(email: email, errCode: response.getBody(key: CoreMessage_TMessageKey.ERRCOD, default_value: "99"))
                                     }
-                                    self.isDismiss?(thumb)
-                                })
-                            })
+                                }
+                            } else {
+                                self.successSubmit(response: response, name: "", email: email)
+                            }
+                        } else {
+                            DispatchQueue.main.async {
+                                self.showFailedSignUpIn(title: "Unable to access servers. Try again later".localized())
+                            }
+                        }
+                    } else {
+                        DispatchQueue.main.async {
+                            self.showFailedSignUpIn(title: "Failed to get auth, please try again".localized())
                         }
                     }
                 } else {
                     DispatchQueue.main.async {
-                        Nexilis.hideLoader(completion: {
-                            let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
-                            imageView.tintColor = .white
-                            let banner = FloatingNotificationBanner(title: "Unable to access servers. Try again later".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
-                            banner.show()
-                        })
+                        self.showFailedSignUpIn(title: "Unable to access servers. Try again later".localized())
                     }
                 }
             }
@@ -189,48 +327,81 @@ public class ChangeDeviceViewController: UIViewController {
         })
     }
     
-    @objc func didTapSubmit(sender: Any) {
-        guard let name = usernameField.text, !name.isEmpty else {
+    private func showFailedSignUpIn(title: String, withLoader: Bool = true) {
+        KeyManagerNexilis.deleteKey()
+        KeyManagerNexilis.deleteMarker()
+        if withLoader {
+            Nexilis.hideLoader(completion: {
+                let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
+                imageView.tintColor = .white
+                let banner = FloatingNotificationBanner(title: title, subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
+                banner.show()
+            })
+        } else {
             let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
             imageView.tintColor = .white
-            let banner = FloatingNotificationBanner(title: "Username can't be empty".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
+            let banner = FloatingNotificationBanner(title: title, subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
             banner.show()
+        }
+    }
+    
+    @objc func didTapSubmit(sender: Any) {
+        guard let name = usernameField.text, !name.isEmpty else {
+            var text = "Username"
+            if isEmail {
+                text = "Email"
+            } else if isMSISDN {
+                text = "Phone Number"
+            }
+            self.showFailedSignUpIn(title: "\(text) can't be empty".localized(), withLoader: false)
             return
         }
-        if isValidEmail(name) {
-            checkEmail(email: name)
+        
+        if isEmail {
+            if !isValidEmail(name) {
+                self.showFailedSignUpIn(title: "Invalid email format. Please enter a valid email address".localized(), withLoader: false)
+            } else {
+                checkEmail(email: name)
+            }
+            return
+        }
+        
+        if isMSISDN {
+            checkNumber(number: name)
             return
         }
         if !name.matches("^[a-zA-Z0-9 ]*$") {
-            let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
-            imageView.tintColor = .white
-            let banner = FloatingNotificationBanner(title: "Contains prohibited characters. Only alphabetic characters are allowed.".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
-            banner.show()
+            self.showFailedSignUpIn(title: "Contains prohibited characters. Only alphabetic characters are allowed.".localized(), withLoader: false)
             return
         }
         guard let password = passwordField.text, !password.isEmpty else {
-            let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
-            imageView.tintColor = .white
-            let banner = FloatingNotificationBanner(title: "Password can't be empty".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
-            banner.show()
+            self.showFailedSignUpIn(title: "Password can't be empty".localized(), withLoader: false)
             return
         }
         if password.count < 6 {
-            let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
-            imageView.tintColor = .white
-            let banner = FloatingNotificationBanner(title: "Password min 6 character".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
-            banner.show()
+            self.showFailedSignUpIn(title: "Password min 6 character".localized(), withLoader: false)
             return
         }
         if !CheckConnection.isConnectedToNetwork() || API.nGetCLXConnState() == 0 {
-            let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
-            imageView.tintColor = .white
-            let banner = FloatingNotificationBanner(title: "Check your connection".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
-            banner.show()
+            self.showFailedSignUpIn(title: "Check your connection".localized(), withLoader: false)
+            return
+        }
+        if KeyManagerNexilis.hasGeneratedKey() {
+            KeyManagerNexilis.deleteKey()
+            KeyManagerNexilis.deleteMarker()
+        }
+        KeyManagerNexilis.generateKey()
+        KeyManagerNexilis.saveMarker()
+        guard let privateKey = KeyManagerNexilis.getPrivateKey() else {
+            KeyManagerNexilis.deleteKey()
+            KeyManagerNexilis.deleteMarker()
+            UIApplication.shared.visibleViewController?.view.makeToast("Biometric or passcode authentication required".localized(), duration: 3, position: .center)
             return
         }
         if Database.shared.openDatabase() == 0 {
             APIS.showRestartApp()
+            KeyManagerNexilis.deleteKey()
+            KeyManagerNexilis.deleteMarker()
             return
         }
         Nexilis.showLoader()
@@ -239,101 +410,97 @@ public class ChangeDeviceViewController: UIViewController {
             if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.getSignIn(p_name: name, p_password: md5Hex), timeout: 30 * 1000) {
                 if response.getBody(key: CoreMessage_TMessageKey.ERRCOD, default_value: "99") == "11" {
                     DispatchQueue.main.async {
-                        Nexilis.hideLoader(completion: {
-                            let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
-                            imageView.tintColor = .white
-                            let banner = FloatingNotificationBanner(title: "Invalid user / Username and password does not match".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
-                            banner.show()
-                        })
+                        self.showFailedSignUpIn(title: "Invalid user / Username and password does not match".localized())
                     }
                 } else if !response.isOk() {
                     DispatchQueue.main.async {
-                        Nexilis.hideLoader(completion: {
-                            let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
-                            imageView.tintColor = .white
-                            let banner = FloatingNotificationBanner(title: "Unable to access servers. Try again later".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
-                            banner.show()
-                        })
+                        self.showFailedSignUpIn(title: "Unable to access servers. Try again later".localized())
                     }
                 } else {
-                    self.deleteAllRecordDatabase()
-                    let id = response.getBody(key: CoreMessage_TMessageKey.F_PIN, default_value: "")
-                    let f_pin = response.getBody(key: CoreMessage_TMessageKey.F_PIN_REAL, default_value: "")
-                    let thumb = response.getBody(key: CoreMessage_TMessageKey.THUMB_ID, default_value: "")
-                    let device_id = response.getBody(key: CoreMessage_TMessageKey.IMEI, default_value: id)
-                    let last_sign = response.getBody(key: CoreMessage_TMessageKey.LAST_SIGN, default_value: "0")
-                    //print("last sign: \(last_sign)")
-                    if last_sign != "0" {
-                        Utils.setLoginMultipleFPin(value: f_pin)
-                        DispatchQueue.main.async {
-                            let imageView = UIImageView(image: UIImage(systemName: "info.circle"))
-                            imageView.tintColor = .white
-                            let banner = FloatingNotificationBanner(title: "Multiple Login Detected...".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .info, colors: nil, iconPosition: .center)
-                            banner.show()
-                        }
-                        DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
-                            Nexilis.hideLoader(completion: {
-                                if Nexilis.showFB {
-                                    Nexilis.floatingButton.removeFromSuperview()
-                                    Nexilis.floatingButton = FloatingButton()
-                                    Nexilis.addFB()
-                                }
-                                NotificationCenter.default.post(name: NSNotification.Name(rawValue: "onRefreshWebView"), object: nil, userInfo: nil)
-                                if !self.forceLogin {
-                                    self.navigationController?.popViewController(animated: true)
-                                } else {
-                                    self.navigationController?.dismiss(animated: true)
-                                }
-                                DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
-                                    let dialog = DialogUnableAccess()
-                                    dialog.modalTransitionStyle = .crossDissolve
-                                    dialog.modalPresentationStyle = .overCurrentContext
-                                    UIApplication.shared.visibleViewController?.present(dialog, animated: true)
-                                })
-                            })
-                        })
-                        return
-                    }
-                    self.deleteAllRecordDatabase()
-                    if(!id.isEmpty) {
-//                            Nexilis.changeUser(f_pin: device_id)
-                        SecureUserDefaults.shared.set(device_id, forKey: "device_id")
-                        Utils.setProfile(value: true)
-                        // pos registration
-                        _ = Nexilis.write(message: CoreMessage_TMessageBank.getPostRegistration(p_pin: id))
-                        DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
-                            Nexilis.hideLoader(completion: {
-                                let imageView = UIImageView(image: UIImage(systemName: "checkmark.circle.fill"))
-                                imageView.tintColor = .white
-                                let banner = FloatingNotificationBanner(title: "Successfully Sign-In".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .success, colors: nil, iconPosition: .center)
-                                banner.show()
-                                if Nexilis.showFB {
-                                    Nexilis.floatingButton.removeFromSuperview()
-                                    Nexilis.floatingButton = FloatingButton()
-                                    Nexilis.addFB()
-                                }
-                                NotificationCenter.default.post(name: NSNotification.Name(rawValue: "onRefreshWebView"), object: nil, userInfo: nil)
-                                if !self.forceLogin {
-                                    self.navigationController?.popViewController(animated: true)
-                                } else {
-                                    self.navigationController?.dismiss(animated: true)
-                                }
-                                self.isDismiss?(thumb)
-                            })
-                        })
-                    }
+                    self.successSubmit(response: response, name: name)
                 }
             } else {
                 DispatchQueue.main.async {
-                    Nexilis.hideLoader(completion: {
-                        let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
-                        imageView.tintColor = .white
-                        let banner = FloatingNotificationBanner(title: "Unable to access servers. Try again later".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
-                        banner.show()
-                    })
+                    self.showFailedSignUpIn(title: "Unable to access servers. Try again later".localized())
                 }
             }
         }
     }
+    
+    private func successSubmit(response: TMessage, name: String, email: String = "", number: String = "") {
+        let id = response.getBody(key: CoreMessage_TMessageKey.F_PIN, default_value: "")
+        let f_pin = response.getBody(key: CoreMessage_TMessageKey.F_PIN_REAL, default_value: "")
+        let device_id = response.getBody(key: CoreMessage_TMessageKey.IMEI, default_value: id)
+        let last_sign = response.getBody(key: CoreMessage_TMessageKey.LAST_SIGN, default_value: "0")
+        //print("last sign: \(last_sign)")
+        if last_sign != "0" {
+            Utils.setLoginMultipleFPin(value: f_pin)
+            DispatchQueue.main.async {
+                let imageView = UIImageView(image: UIImage(systemName: "info.circle"))
+                imageView.tintColor = .white
+                let banner = FloatingNotificationBanner(title: "Multiple Login Detected...".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .info, colors: nil, iconPosition: .center)
+                banner.show()
+            }
+            DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
+                Nexilis.hideLoader(completion: {
+                    if Nexilis.showFB {
+                        Nexilis.floatingButton.removeFromSuperview()
+                        Nexilis.floatingButton = FloatingButton()
+                        Nexilis.addFB()
+                    }
+                    NotificationCenter.default.post(name: NSNotification.Name(rawValue: "onRefreshWebView"), object: nil, userInfo: nil)
+                    if self.forceLogin {
+                        self.navigationController?.dismiss(animated: true)
+                    } else {
+                        let controllers = self.navigationController?.viewControllers
+                        if controllers![controllers!.count - 2] is SignInOption {
+                            self.navigationController?.popToViewController(controllers![0], animated: true)
+                        } else {
+                            self.navigationController?.popViewController(animated: true)
+                        }
+                    }
+                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
+                        let dialog = DialogUnableAccess()
+                        dialog.modalTransitionStyle = .crossDissolve
+                        dialog.modalPresentationStyle = .overCurrentContext
+                        UIApplication.shared.visibleViewController?.present(dialog, animated: true)
+                    })
+                })
+            })
+            return
+        }
+        self.deleteAllRecordDatabase()
+        if(!id.isEmpty) {
+//                            Nexilis.changeUser(f_pin: device_id)
+            SecureUserDefaults.shared.set(device_id, forKey: "device_id")
+            Utils.setProfile(value: true)
+            // pos registration
+            _ = Nexilis.write(message: CoreMessage_TMessageBank.getPostRegistration(p_pin: id))
+            DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
+                Nexilis.hideLoader(completion: {
+                    let imageView = UIImageView(image: UIImage(systemName: "checkmark.circle.fill"))
+                    imageView.tintColor = .white
+                    let banner = FloatingNotificationBanner(title: "Successfully Sign-In".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .success, colors: nil, iconPosition: .center)
+                    banner.show()
+                    if Nexilis.showFB {
+                        Nexilis.floatingButton.removeFromSuperview()
+                        Nexilis.floatingButton = FloatingButton()
+                        Nexilis.addFB()
+                    }
+                    NotificationCenter.default.post(name: NSNotification.Name(rawValue: "onRefreshWebView"), object: nil, userInfo: nil)
+                    if self.forceLogin {
+                        self.navigationController?.dismiss(animated: true)
+                    } else {
+                        let controllers = self.navigationController?.viewControllers
+                        if controllers![controllers!.count - 2] is SignInOption {
+                            self.navigationController?.popToViewController(controllers![controllers!.count - 3], animated: true)
+                        } else {
+                            self.navigationController?.popViewController(animated: true)
+                        }
+                    }
+                })
+            })
+        }
+    }
 
 }

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

@@ -279,7 +279,7 @@ class ContactChatViewController: UITableViewController {
                 self?.timerReloadData?.invalidate()
                 self?.timerReloadData = nil
                 self?.timerReloadData = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { [weak self] _ in
-                    if !self!.isGettingData {
+                    if self != nil && !self!.isGettingData {
                         self?.getData()
                         self?.timerReloadData = nil
                     }

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

@@ -623,11 +623,7 @@ public class SettingTableViewController: UITableViewController, UIGestureRecogni
         } else if item.title == "Chat Wallpaper".localized() {
             APIS.openChatWallpaper()
         } else if item.title == "Sign-In".localized() {
-            let controller = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "changeDevice") as! ChangeDeviceViewController
-            controller.isDismiss = { newThumb in
-                self.makeMenu(imageSignIn: newThumb)
-                self.tableView.reloadData()
-            }
+            guard let controller = APIS.getControllerSign(forceSignIn: true) else { return }
             navigationController?.show(controller, sender: nil)
         } else if item.title == "Sign-Up/Sign-In".localized() {
             guard let controller = APIS.getControllerSign() else { return }

+ 30 - 10
NexilisLite/NexilisLite/Source/View/Control/SignInOption.swift

@@ -10,12 +10,17 @@ import Foundation
 
 public class SignInOption: UIViewController {
     public var forceLogin = false
+    public var forceSignIn = false
     
     private let containerView = UIStackView()
     
     public override func viewDidLoad() {
-        self.title = "Sign-Up/Sign-In Method".localized()
-        if forceLogin {
+        if forceSignIn {
+            self.title = "Sign-In Method".localized()
+        } else {
+            self.title = "Sign-Up/Sign-In Method".localized()
+        }
+        if forceLogin && !forceSignIn {
             navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(didTapCancel(sender:)))
         }
         self.navigationController?.navigationBar.topItem?.backButtonTitle = ""
@@ -50,7 +55,11 @@ public class SignInOption: UIViewController {
         let title = UILabel()
         self.view.addSubview(title)
         title.anchor(top: iconIV.bottomAnchor, paddingTop: 10, centerX: iconIV.centerXAnchor)
-        title.text = "Choose your Sign-Up or Sign-In method :".localized()
+        if forceSignIn {
+            title.text = "Choose your Sign-In method :".localized()
+        } else {
+            title.text = "Choose your Sign-Up or Sign-In method :".localized()
+        }
         title.font = .systemFont(ofSize: 14)
         
         let bottomTitle = UILabel()
@@ -104,14 +113,25 @@ public class SignInOption: UIViewController {
     }
     
     @objc func didTapSignIn(sender: UIButton) {
-        let vc = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "signupsignin") as! SignUpSignIn
-        if sender.tag == 0 {
-            vc.isMSISDN = true
-        } else if sender.tag == 1 {
-            vc.isEmail = true
+        if !forceSignIn {
+            let vc = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "signupsignin") as! SignUpSignIn
+            if sender.tag == 0 {
+                vc.isMSISDN = true
+            } else if sender.tag == 1 {
+                vc.isEmail = true
+            }
+            vc.forceLogin = self.forceLogin
+            self.show(vc, sender: nil)
+        } else {
+            let vc = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "changeDevice") as! ChangeDeviceViewController
+            if sender.tag == 0 {
+                vc.isMSISDN = true
+            } else if sender.tag == 1 {
+                vc.isEmail = true
+            }
+            vc.forceLogin = self.forceLogin
+            self.show(vc, sender: nil)
         }
-        vc.forceLogin = self.forceLogin
-        self.show(vc, sender: nil)
     }
     
     @objc func didTapCancel(sender: Any) {

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

@@ -296,7 +296,9 @@ public class SignUpSignIn: UIViewController {
                         if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.getSendVerifyChangeDevice(p_email: email, p_vercode: code, deviceFingerprint: df, publicKey: pk, signature: sign), timeout: 30 * 1000) {
                             if !response.isOk() {
                                 DispatchQueue.main.async {
-                                    self.showPageOTP(email: email, errCode: response.getBody(key: CoreMessage_TMessageKey.ERRCOD, default_value: "99"))
+                                    Nexilis.hideLoader {
+                                        self.showPageOTP(email: email, errCode: response.getBody(key: CoreMessage_TMessageKey.ERRCOD, default_value: "99"))
+                                    }
                                 }
                             } else {
                                 self.successSubmit(response: response, first: "", last: "", email: email)

+ 1 - 0
NexilisLite/Podfile

@@ -17,6 +17,7 @@ target 'NexilisLite' do
   pod 'SwiftLinkPreview', '~> 3.4.0'
   pod 'Popover', '~> 1.3.0'
   pod 'KeychainAccess', '~> 4.2.2'
+  pod 'Firebase/Auth', '~> 11.14.0'
 
   target 'NexilisLiteTests' do
     # Pods for testing