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

update add uclist hierarchy and add secure browser

alqindiirsyam 6 сар өмнө
parent
commit
dd4acb8e67

BIN
.DS_Store


BIN
AppBuilder/.DS_Store


+ 47 - 58
AppBuilder/AppBuilder.xcodeproj/project.pbxproj

@@ -14,12 +14,11 @@
 		2401CEA1275490DB00B323BB /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2401CE9F275490DB00B323BB /* Main.storyboard */; };
 		2401CEA1275490DB00B323BB /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2401CE9F275490DB00B323BB /* Main.storyboard */; };
 		2401CEA3275490E600B323BB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2401CEA2275490E600B323BB /* Assets.xcassets */; };
 		2401CEA3275490E600B323BB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2401CEA2275490E600B323BB /* Assets.xcassets */; };
 		2401CEA6275490E600B323BB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2401CEA4275490E600B323BB /* LaunchScreen.storyboard */; };
 		2401CEA6275490E600B323BB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2401CEA4275490E600B323BB /* LaunchScreen.storyboard */; };
-		58945074FC459F87D8C4EDA4 /* Pods_AppBuilder.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D246950EF11B497A8C2CE4B7 /* Pods_AppBuilder.framework */; };
 		A413B18727EACB20006D16EB /* PrefsUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = A413B18627EACB20006D16EB /* PrefsUtil.swift */; };
 		A413B18727EACB20006D16EB /* PrefsUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = A413B18627EACB20006D16EB /* PrefsUtil.swift */; };
 		A42ED92227F30BA200B0FAB7 /* FirstTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A42ED92127F30BA200B0FAB7 /* FirstTabViewController.swift */; };
 		A42ED92227F30BA200B0FAB7 /* FirstTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A42ED92127F30BA200B0FAB7 /* FirstTabViewController.swift */; };
 		A42ED92427F3FC2F00B0FAB7 /* SecondTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A42ED92327F3FC2F00B0FAB7 /* SecondTabViewController.swift */; };
 		A42ED92427F3FC2F00B0FAB7 /* SecondTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A42ED92327F3FC2F00B0FAB7 /* SecondTabViewController.swift */; };
 		A42ED92627F439A200B0FAB7 /* ThirdTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A42ED92527F439A200B0FAB7 /* ThirdTabViewController.swift */; };
 		A42ED92627F439A200B0FAB7 /* ThirdTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A42ED92527F439A200B0FAB7 /* ThirdTabViewController.swift */; };
-		CD865A8A2D5B17B2007F741E /* AppBuilderShare.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = CD865A802D5B17B2007F741E /* AppBuilderShare.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+		CD08A1682D5EEDA5005B4EAC /* AppBuilderShare.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = CD08A15E2D5EEDA5005B4EAC /* AppBuilderShare.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
 		CD9D59D92BEE1D30008014B4 /* digisales_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = CD9D59CE2BEE1D2F008014B4 /* digisales_icon.png */; };
 		CD9D59D92BEE1D30008014B4 /* digisales_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = CD9D59CE2BEE1D2F008014B4 /* digisales_icon.png */; };
 		CD9D59DA2BEE1D30008014B4 /* ikn_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = CD9D59CF2BEE1D2F008014B4 /* ikn_icon.png */; };
 		CD9D59DA2BEE1D30008014B4 /* ikn_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = CD9D59CF2BEE1D2F008014B4 /* ikn_icon.png */; };
 		CD9D59DB2BEE1D30008014B4 /* gudeg_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = CD9D59D02BEE1D2F008014B4 /* gudeg_icon.png */; };
 		CD9D59DB2BEE1D30008014B4 /* gudeg_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = CD9D59D02BEE1D2F008014B4 /* gudeg_icon.png */; };
@@ -31,14 +30,15 @@
 		CD9D59E12BEE1D30008014B4 /* disini_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = CD9D59D62BEE1D30008014B4 /* disini_icon.png */; };
 		CD9D59E12BEE1D30008014B4 /* disini_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = CD9D59D62BEE1D30008014B4 /* disini_icon.png */; };
 		CD9D59E22BEE1D30008014B4 /* kmi_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = CD9D59D72BEE1D30008014B4 /* kmi_icon.png */; };
 		CD9D59E22BEE1D30008014B4 /* kmi_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = CD9D59D72BEE1D30008014B4 /* kmi_icon.png */; };
 		CD9D59E32BEE1D30008014B4 /* nu_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = CD9D59D82BEE1D30008014B4 /* nu_icon.png */; };
 		CD9D59E32BEE1D30008014B4 /* nu_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = CD9D59D82BEE1D30008014B4 /* nu_icon.png */; };
+		D6B1C354D062B60F7CAABAED /* Pods_AppBuilder.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9E273714C9F2C0AFEADA842F /* Pods_AppBuilder.framework */; };
 /* End PBXBuildFile section */
 /* End PBXBuildFile section */
 
 
 /* Begin PBXContainerItemProxy section */
 /* Begin PBXContainerItemProxy section */
-		CD865A882D5B17B2007F741E /* PBXContainerItemProxy */ = {
+		CD08A1662D5EEDA5005B4EAC /* PBXContainerItemProxy */ = {
 			isa = PBXContainerItemProxy;
 			isa = PBXContainerItemProxy;
 			containerPortal = 2401CE8E275490DB00B323BB /* Project object */;
 			containerPortal = 2401CE8E275490DB00B323BB /* Project object */;
 			proxyType = 1;
 			proxyType = 1;
-			remoteGlobalIDString = CD865A7F2D5B17B2007F741E;
+			remoteGlobalIDString = CD08A15D2D5EEDA5005B4EAC;
 			remoteInfo = AppBuilderShare;
 			remoteInfo = AppBuilderShare;
 		};
 		};
 /* End PBXContainerItemProxy section */
 /* End PBXContainerItemProxy section */
@@ -54,23 +54,13 @@
 			name = "Embed Frameworks";
 			name = "Embed Frameworks";
 			runOnlyForDeploymentPostprocessing = 0;
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 		};
-		CD865AA62D5B2325007F741E /* Embed Frameworks */ = {
-			isa = PBXCopyFilesBuildPhase;
-			buildActionMask = 2147483647;
-			dstPath = "";
-			dstSubfolderSpec = 10;
-			files = (
-			);
-			name = "Embed Frameworks";
-			runOnlyForDeploymentPostprocessing = 0;
-		};
 		CDEE3DD129B06E1E00B420E5 /* Embed Foundation Extensions */ = {
 		CDEE3DD129B06E1E00B420E5 /* Embed Foundation Extensions */ = {
 			isa = PBXCopyFilesBuildPhase;
 			isa = PBXCopyFilesBuildPhase;
 			buildActionMask = 2147483647;
 			buildActionMask = 2147483647;
 			dstPath = "";
 			dstPath = "";
 			dstSubfolderSpec = 13;
 			dstSubfolderSpec = 13;
 			files = (
 			files = (
-				CD865A8A2D5B17B2007F741E /* AppBuilderShare.appex in Embed Foundation Extensions */,
+				CD08A1682D5EEDA5005B4EAC /* AppBuilderShare.appex in Embed Foundation Extensions */,
 			);
 			);
 			name = "Embed Foundation Extensions";
 			name = "Embed Foundation Extensions";
 			runOnlyForDeploymentPostprocessing = 0;
 			runOnlyForDeploymentPostprocessing = 0;
@@ -78,7 +68,6 @@
 /* End PBXCopyFilesBuildPhase section */
 /* End PBXCopyFilesBuildPhase section */
 
 
 /* Begin PBXFileReference section */
 /* Begin PBXFileReference section */
-		005671E57652801092CB2F74 /* Pods-AppBuilder.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppBuilder.debug.xcconfig"; path = "Target Support Files/Pods-AppBuilder/Pods-AppBuilder.debug.xcconfig"; sourceTree = "<group>"; };
 		12960ADF2892361000A467DD /* FourthTabViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FourthTabViewController.swift; sourceTree = "<group>"; };
 		12960ADF2892361000A467DD /* FourthTabViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FourthTabViewController.swift; sourceTree = "<group>"; };
 		2401CE96275490DB00B323BB /* AppBuilder.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AppBuilder.app; sourceTree = BUILT_PRODUCTS_DIR; };
 		2401CE96275490DB00B323BB /* AppBuilder.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AppBuilder.app; sourceTree = BUILT_PRODUCTS_DIR; };
 		2401CE99275490DB00B323BB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
 		2401CE99275490DB00B323BB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
@@ -88,12 +77,14 @@
 		2401CEA2275490E600B323BB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
 		2401CEA2275490E600B323BB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
 		2401CEA5275490E600B323BB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
 		2401CEA5275490E600B323BB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
 		2401CEA7275490E600B323BB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		2401CEA7275490E600B323BB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
-		77BA1C01F16978F06C6E2031 /* Pods-AppBuilder.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppBuilder.release.xcconfig"; path = "Target Support Files/Pods-AppBuilder/Pods-AppBuilder.release.xcconfig"; sourceTree = "<group>"; };
+		4CBBF6A59F76BC3D5DA5B92B /* Pods-AppBuilder.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppBuilder.debug.xcconfig"; path = "Target Support Files/Pods-AppBuilder/Pods-AppBuilder.debug.xcconfig"; sourceTree = "<group>"; };
+		65A3907E764F550D0F7DD37D /* Pods-AppBuilder.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppBuilder.release.xcconfig"; path = "Target Support Files/Pods-AppBuilder/Pods-AppBuilder.release.xcconfig"; sourceTree = "<group>"; };
+		9E273714C9F2C0AFEADA842F /* Pods_AppBuilder.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AppBuilder.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		A413B18627EACB20006D16EB /* PrefsUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsUtil.swift; sourceTree = "<group>"; };
 		A413B18627EACB20006D16EB /* PrefsUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsUtil.swift; sourceTree = "<group>"; };
 		A42ED92127F30BA200B0FAB7 /* FirstTabViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstTabViewController.swift; sourceTree = "<group>"; };
 		A42ED92127F30BA200B0FAB7 /* FirstTabViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstTabViewController.swift; sourceTree = "<group>"; };
 		A42ED92327F3FC2F00B0FAB7 /* SecondTabViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondTabViewController.swift; sourceTree = "<group>"; };
 		A42ED92327F3FC2F00B0FAB7 /* SecondTabViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondTabViewController.swift; sourceTree = "<group>"; };
 		A42ED92527F439A200B0FAB7 /* ThirdTabViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdTabViewController.swift; sourceTree = "<group>"; };
 		A42ED92527F439A200B0FAB7 /* ThirdTabViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdTabViewController.swift; sourceTree = "<group>"; };
-		CD865A802D5B17B2007F741E /* AppBuilderShare.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = AppBuilderShare.appex; sourceTree = BUILT_PRODUCTS_DIR; };
+		CD08A15E2D5EEDA5005B4EAC /* AppBuilderShare.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = AppBuilderShare.appex; sourceTree = BUILT_PRODUCTS_DIR; };
 		CD9D59CE2BEE1D2F008014B4 /* digisales_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = digisales_icon.png; sourceTree = "<group>"; };
 		CD9D59CE2BEE1D2F008014B4 /* digisales_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = digisales_icon.png; sourceTree = "<group>"; };
 		CD9D59CF2BEE1D2F008014B4 /* ikn_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ikn_icon.png; sourceTree = "<group>"; };
 		CD9D59CF2BEE1D2F008014B4 /* ikn_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ikn_icon.png; sourceTree = "<group>"; };
 		CD9D59D02BEE1D2F008014B4 /* gudeg_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = gudeg_icon.png; sourceTree = "<group>"; };
 		CD9D59D02BEE1D2F008014B4 /* gudeg_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = gudeg_icon.png; sourceTree = "<group>"; };
@@ -106,21 +97,20 @@
 		CD9D59D72BEE1D30008014B4 /* kmi_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = kmi_icon.png; sourceTree = "<group>"; };
 		CD9D59D72BEE1D30008014B4 /* kmi_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = kmi_icon.png; sourceTree = "<group>"; };
 		CD9D59D82BEE1D30008014B4 /* nu_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = nu_icon.png; sourceTree = "<group>"; };
 		CD9D59D82BEE1D30008014B4 /* nu_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = nu_icon.png; sourceTree = "<group>"; };
 		CDE27BA42D53641D006298BD /* AppBuilder.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AppBuilder.entitlements; sourceTree = "<group>"; };
 		CDE27BA42D53641D006298BD /* AppBuilder.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AppBuilder.entitlements; sourceTree = "<group>"; };
-		D246950EF11B497A8C2CE4B7 /* Pods_AppBuilder.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AppBuilder.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 /* End PBXFileReference section */
 /* End PBXFileReference section */
 
 
 /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
 /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
-		CD865A8B2D5B17B2007F741E /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
+		CD08A1692D5EEDA5005B4EAC /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
 			isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
 			isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
 			membershipExceptions = (
 			membershipExceptions = (
 				Info.plist,
 				Info.plist,
 			);
 			);
-			target = CD865A7F2D5B17B2007F741E /* AppBuilderShare */;
+			target = CD08A15D2D5EEDA5005B4EAC /* AppBuilderShare */;
 		};
 		};
 /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
 /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
 
 
 /* Begin PBXFileSystemSynchronizedRootGroup section */
 /* Begin PBXFileSystemSynchronizedRootGroup section */
-		CD865A812D5B17B2007F741E /* AppBuilderShare */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (CD865A8B2D5B17B2007F741E /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = AppBuilderShare; sourceTree = "<group>"; };
+		CD08A15F2D5EEDA5005B4EAC /* AppBuilderShare */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (CD08A1692D5EEDA5005B4EAC /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = AppBuilderShare; sourceTree = "<group>"; };
 /* End PBXFileSystemSynchronizedRootGroup section */
 /* End PBXFileSystemSynchronizedRootGroup section */
 
 
 /* Begin PBXFrameworksBuildPhase section */
 /* Begin PBXFrameworksBuildPhase section */
@@ -128,11 +118,11 @@
 			isa = PBXFrameworksBuildPhase;
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			buildActionMask = 2147483647;
 			files = (
 			files = (
-				58945074FC459F87D8C4EDA4 /* Pods_AppBuilder.framework in Frameworks */,
+				D6B1C354D062B60F7CAABAED /* Pods_AppBuilder.framework in Frameworks */,
 			);
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 		};
-		CD865A7D2D5B17B2007F741E /* Frameworks */ = {
+		CD08A15B2D5EEDA5005B4EAC /* Frameworks */ = {
 			isa = PBXFrameworksBuildPhase;
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			buildActionMask = 2147483647;
 			files = (
 			files = (
@@ -146,8 +136,8 @@
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
 				2401CE98275490DB00B323BB /* AppBuilder */,
 				2401CE98275490DB00B323BB /* AppBuilder */,
-				CD865A812D5B17B2007F741E /* AppBuilderShare */,
-				76BBCA5783FCBB787B29FC82 /* Frameworks */,
+				CD08A15F2D5EEDA5005B4EAC /* AppBuilderShare */,
+				D5D9E949851220D48FDB3E78 /* Frameworks */,
 				6E32BCCF4DE50EE1A90E8AAE /* Pods */,
 				6E32BCCF4DE50EE1A90E8AAE /* Pods */,
 				2401CE97275490DB00B323BB /* Products */,
 				2401CE97275490DB00B323BB /* Products */,
 			);
 			);
@@ -157,7 +147,7 @@
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
 				2401CE96275490DB00B323BB /* AppBuilder.app */,
 				2401CE96275490DB00B323BB /* AppBuilder.app */,
-				CD865A802D5B17B2007F741E /* AppBuilderShare.appex */,
+				CD08A15E2D5EEDA5005B4EAC /* AppBuilderShare.appex */,
 			);
 			);
 			name = Products;
 			name = Products;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
@@ -196,16 +186,16 @@
 		6E32BCCF4DE50EE1A90E8AAE /* Pods */ = {
 		6E32BCCF4DE50EE1A90E8AAE /* Pods */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
-				005671E57652801092CB2F74 /* Pods-AppBuilder.debug.xcconfig */,
-				77BA1C01F16978F06C6E2031 /* Pods-AppBuilder.release.xcconfig */,
+				4CBBF6A59F76BC3D5DA5B92B /* Pods-AppBuilder.debug.xcconfig */,
+				65A3907E764F550D0F7DD37D /* Pods-AppBuilder.release.xcconfig */,
 			);
 			);
 			path = Pods;
 			path = Pods;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
 		};
 		};
-		76BBCA5783FCBB787B29FC82 /* Frameworks */ = {
+		D5D9E949851220D48FDB3E78 /* Frameworks */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
-				D246950EF11B497A8C2CE4B7 /* Pods_AppBuilder.framework */,
+				9E273714C9F2C0AFEADA842F /* Pods_AppBuilder.framework */,
 			);
 			);
 			name = Frameworks;
 			name = Frameworks;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
@@ -217,45 +207,44 @@
 			isa = PBXNativeTarget;
 			isa = PBXNativeTarget;
 			buildConfigurationList = 2401CEC0275490E600B323BB /* Build configuration list for PBXNativeTarget "AppBuilder" */;
 			buildConfigurationList = 2401CEC0275490E600B323BB /* Build configuration list for PBXNativeTarget "AppBuilder" */;
 			buildPhases = (
 			buildPhases = (
-				7B7ED6AEAA0D03CB68EFC23A /* [CP] Check Pods Manifest.lock */,
+				BF5B1FC69F69170CAEF49C91 /* [CP] Check Pods Manifest.lock */,
 				2401CE92275490DB00B323BB /* Sources */,
 				2401CE92275490DB00B323BB /* Sources */,
 				2401CE93275490DB00B323BB /* Frameworks */,
 				2401CE93275490DB00B323BB /* Frameworks */,
 				2401CE94275490DB00B323BB /* Resources */,
 				2401CE94275490DB00B323BB /* Resources */,
 				247E0A722796969200430E5F /* Embed Frameworks */,
 				247E0A722796969200430E5F /* Embed Frameworks */,
 				CDEE3DD129B06E1E00B420E5 /* Embed Foundation Extensions */,
 				CDEE3DD129B06E1E00B420E5 /* Embed Foundation Extensions */,
-				6237F8E2B28CDC232617A789 /* [CP] Embed Pods Frameworks */,
+				07F1B34C1BB97DF342E2A084 /* [CP] Embed Pods Frameworks */,
 			);
 			);
 			buildRules = (
 			buildRules = (
 			);
 			);
 			dependencies = (
 			dependencies = (
-				CD865A892D5B17B2007F741E /* PBXTargetDependency */,
+				CD08A1672D5EEDA5005B4EAC /* PBXTargetDependency */,
 			);
 			);
 			name = AppBuilder;
 			name = AppBuilder;
 			productName = TestQmeraLite;
 			productName = TestQmeraLite;
 			productReference = 2401CE96275490DB00B323BB /* AppBuilder.app */;
 			productReference = 2401CE96275490DB00B323BB /* AppBuilder.app */;
 			productType = "com.apple.product-type.application";
 			productType = "com.apple.product-type.application";
 		};
 		};
-		CD865A7F2D5B17B2007F741E /* AppBuilderShare */ = {
+		CD08A15D2D5EEDA5005B4EAC /* AppBuilderShare */ = {
 			isa = PBXNativeTarget;
 			isa = PBXNativeTarget;
-			buildConfigurationList = CD865A8C2D5B17B2007F741E /* Build configuration list for PBXNativeTarget "AppBuilderShare" */;
+			buildConfigurationList = CD08A16A2D5EEDA5005B4EAC /* Build configuration list for PBXNativeTarget "AppBuilderShare" */;
 			buildPhases = (
 			buildPhases = (
-				CD865A7C2D5B17B2007F741E /* Sources */,
-				CD865A7D2D5B17B2007F741E /* Frameworks */,
-				CD865A7E2D5B17B2007F741E /* Resources */,
-				CD865AA62D5B2325007F741E /* Embed Frameworks */,
+				CD08A15A2D5EEDA5005B4EAC /* Sources */,
+				CD08A15B2D5EEDA5005B4EAC /* Frameworks */,
+				CD08A15C2D5EEDA5005B4EAC /* Resources */,
 			);
 			);
 			buildRules = (
 			buildRules = (
 			);
 			);
 			dependencies = (
 			dependencies = (
 			);
 			);
 			fileSystemSynchronizedGroups = (
 			fileSystemSynchronizedGroups = (
-				CD865A812D5B17B2007F741E /* AppBuilderShare */,
+				CD08A15F2D5EEDA5005B4EAC /* AppBuilderShare */,
 			);
 			);
 			name = AppBuilderShare;
 			name = AppBuilderShare;
 			packageProductDependencies = (
 			packageProductDependencies = (
 			);
 			);
 			productName = AppBuilderShare;
 			productName = AppBuilderShare;
-			productReference = CD865A802D5B17B2007F741E /* AppBuilderShare.appex */;
+			productReference = CD08A15E2D5EEDA5005B4EAC /* AppBuilderShare.appex */;
 			productType = "com.apple.product-type.app-extension";
 			productType = "com.apple.product-type.app-extension";
 		};
 		};
 /* End PBXNativeTarget section */
 /* End PBXNativeTarget section */
@@ -270,7 +259,7 @@
 					2401CE95275490DB00B323BB = {
 					2401CE95275490DB00B323BB = {
 						CreatedOnToolsVersion = 12.5.1;
 						CreatedOnToolsVersion = 12.5.1;
 					};
 					};
-					CD865A7F2D5B17B2007F741E = {
+					CD08A15D2D5EEDA5005B4EAC = {
 						CreatedOnToolsVersion = 16.2;
 						CreatedOnToolsVersion = 16.2;
 					};
 					};
 				};
 				};
@@ -289,7 +278,7 @@
 			projectRoot = "";
 			projectRoot = "";
 			targets = (
 			targets = (
 				2401CE95275490DB00B323BB /* AppBuilder */,
 				2401CE95275490DB00B323BB /* AppBuilder */,
-				CD865A7F2D5B17B2007F741E /* AppBuilderShare */,
+				CD08A15D2D5EEDA5005B4EAC /* AppBuilderShare */,
 			);
 			);
 		};
 		};
 /* End PBXProject section */
 /* End PBXProject section */
@@ -316,7 +305,7 @@
 			);
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 		};
-		CD865A7E2D5B17B2007F741E /* Resources */ = {
+		CD08A15C2D5EEDA5005B4EAC /* Resources */ = {
 			isa = PBXResourcesBuildPhase;
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			buildActionMask = 2147483647;
 			files = (
 			files = (
@@ -326,7 +315,7 @@
 /* End PBXResourcesBuildPhase section */
 /* End PBXResourcesBuildPhase section */
 
 
 /* Begin PBXShellScriptBuildPhase section */
 /* Begin PBXShellScriptBuildPhase section */
-		6237F8E2B28CDC232617A789 /* [CP] Embed Pods Frameworks */ = {
+		07F1B34C1BB97DF342E2A084 /* [CP] Embed Pods Frameworks */ = {
 			isa = PBXShellScriptBuildPhase;
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			buildActionMask = 2147483647;
 			files = (
 			files = (
@@ -343,7 +332,7 @@
 			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AppBuilder/Pods-AppBuilder-frameworks.sh\"\n";
 			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AppBuilder/Pods-AppBuilder-frameworks.sh\"\n";
 			showEnvVarsInLog = 0;
 			showEnvVarsInLog = 0;
 		};
 		};
-		7B7ED6AEAA0D03CB68EFC23A /* [CP] Check Pods Manifest.lock */ = {
+		BF5B1FC69F69170CAEF49C91 /* [CP] Check Pods Manifest.lock */ = {
 			isa = PBXShellScriptBuildPhase;
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			buildActionMask = 2147483647;
 			files = (
 			files = (
@@ -383,7 +372,7 @@
 			);
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 		};
-		CD865A7C2D5B17B2007F741E /* Sources */ = {
+		CD08A15A2D5EEDA5005B4EAC /* Sources */ = {
 			isa = PBXSourcesBuildPhase;
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			buildActionMask = 2147483647;
 			files = (
 			files = (
@@ -393,10 +382,10 @@
 /* End PBXSourcesBuildPhase section */
 /* End PBXSourcesBuildPhase section */
 
 
 /* Begin PBXTargetDependency section */
 /* Begin PBXTargetDependency section */
-		CD865A892D5B17B2007F741E /* PBXTargetDependency */ = {
+		CD08A1672D5EEDA5005B4EAC /* PBXTargetDependency */ = {
 			isa = PBXTargetDependency;
 			isa = PBXTargetDependency;
-			target = CD865A7F2D5B17B2007F741E /* AppBuilderShare */;
-			targetProxy = CD865A882D5B17B2007F741E /* PBXContainerItemProxy */;
+			target = CD08A15D2D5EEDA5005B4EAC /* AppBuilderShare */;
+			targetProxy = CD08A1662D5EEDA5005B4EAC /* PBXContainerItemProxy */;
 		};
 		};
 /* End PBXTargetDependency section */
 /* End PBXTargetDependency section */
 
 
@@ -538,7 +527,7 @@
 		};
 		};
 		2401CEC1275490E600B323BB /* Debug */ = {
 		2401CEC1275490E600B323BB /* Debug */ = {
 			isa = XCBuildConfiguration;
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 005671E57652801092CB2F74 /* Pods-AppBuilder.debug.xcconfig */;
+			baseConfigurationReference = 4CBBF6A59F76BC3D5DA5B92B /* Pods-AppBuilder.debug.xcconfig */;
 			buildSettings = {
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
@@ -574,7 +563,7 @@
 		};
 		};
 		2401CEC2275490E600B323BB /* Release */ = {
 		2401CEC2275490E600B323BB /* Release */ = {
 			isa = XCBuildConfiguration;
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 77BA1C01F16978F06C6E2031 /* Pods-AppBuilder.release.xcconfig */;
+			baseConfigurationReference = 65A3907E764F550D0F7DD37D /* Pods-AppBuilder.release.xcconfig */;
 			buildSettings = {
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
@@ -608,7 +597,7 @@
 			};
 			};
 			name = Release;
 			name = Release;
 		};
 		};
-		CD865A8D2D5B17B2007F741E /* Debug */ = {
+		CD08A16B2D5EEDA5005B4EAC /* Debug */ = {
 			isa = XCBuildConfiguration;
 			isa = XCBuildConfiguration;
 			buildSettings = {
 			buildSettings = {
 				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
 				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
@@ -646,7 +635,7 @@
 			};
 			};
 			name = Debug;
 			name = Debug;
 		};
 		};
-		CD865A8E2D5B17B2007F741E /* Release */ = {
+		CD08A16C2D5EEDA5005B4EAC /* Release */ = {
 			isa = XCBuildConfiguration;
 			isa = XCBuildConfiguration;
 			buildSettings = {
 			buildSettings = {
 				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
 				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
@@ -704,11 +693,11 @@
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Release;
 			defaultConfigurationName = Release;
 		};
 		};
-		CD865A8C2D5B17B2007F741E /* Build configuration list for PBXNativeTarget "AppBuilderShare" */ = {
+		CD08A16A2D5EEDA5005B4EAC /* Build configuration list for PBXNativeTarget "AppBuilderShare" */ = {
 			isa = XCConfigurationList;
 			isa = XCConfigurationList;
 			buildConfigurations = (
 			buildConfigurations = (
-				CD865A8D2D5B17B2007F741E /* Debug */,
-				CD865A8E2D5B17B2007F741E /* Release */,
+				CD08A16B2D5EEDA5005B4EAC /* Debug */,
+				CD08A16C2D5EEDA5005B4EAC /* Release */,
 			);
 			);
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Release;
 			defaultConfigurationName = Release;

+ 256 - 166
AppBuilder/AppBuilder/SecondTabViewController.swift

@@ -18,6 +18,7 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
     var isAdmin: Bool = false
     var isAdmin: Bool = false
     
     
     var chats: [Chat] = []
     var chats: [Chat] = []
+    var chatGroupMaps: [String: [Chat]] = [:]
     
     
     var groups: [Group] = []
     var groups: [Group] = []
     
     
@@ -34,6 +35,8 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
     var alertController = LibAlertController()
     var alertController = LibAlertController()
     
     
     var noData = false
     var noData = false
+    var loadingData = true
+    var waitingLoading = false
     
     
     let textViewSearch = UITextField()
     let textViewSearch = UITextField()
     let buttonImageVoiceSb = UIButton(type: .custom)
     let buttonImageVoiceSb = UIButton(type: .custom)
@@ -113,7 +116,12 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
                 fillteredData = self.groups.filter { $0.name.lowercased().contains(searchText.lowercased()) }
                 fillteredData = self.groups.filter { $0.name.lowercased().contains(searchText.lowercased()) }
             default:
             default:
                 if selectedTag == 0 {
                 if selectedTag == 0 {
-                    fillteredData = self.chats.filter { $0.name.lowercased().contains(searchText.lowercased()) || $0.messageText.lowercased().contains(searchText.lowercased()) }
+                    var group_id: String?
+                    if let filterGroupKey = self.chatGroupMaps.first(where: { $0.value.contains { $0.name.lowercased().contains(searchText.lowercased()) || $0.groupName.lowercased().contains(searchText.lowercased()) || $0.messageText.lowercased().contains(searchText.lowercased()) } } ) {
+                        group_id = filterGroupKey.key
+                    }
+                    let deepCopyChats = self.chats.map{ $0.copy() }
+                    fillteredData = deepCopyChats.filter { $0.name.lowercased().contains(searchText.lowercased()) || $0.messageText.lowercased().contains(searchText.lowercased()) || $0.groupId == group_id }
                 } else {
                 } else {
                     switch(selectedTag) {
                     switch(selectedTag) {
                     case UNREAD_TAG :
                     case UNREAD_TAG :
@@ -359,20 +367,7 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
     }
     }
     
     
     @objc func startConversation(){
     @objc func startConversation(){
-        let navigationController = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "contactChatNav") as! UINavigationController
-        navigationController.view.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .black : .white
-        Utils.addBackground(view: navigationController.view)
-        navigationController.modalPresentationStyle = .fullScreen
-        navigationController.navigationBar.tintColor = .white
-        navigationController.navigationBar.barTintColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
-        navigationController.navigationBar.isTranslucent = false
-        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
-        navigationController.navigationBar.overrideUserInterfaceStyle = .dark
-        navigationController.navigationBar.barStyle = .black
-        self.navigationController?.present(navigationController, animated: true, completion: nil)
+        APIS.openChat()
     }
     }
     
     
     @objc func recordAudio(){
     @objc func recordAudio(){
@@ -622,23 +617,9 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
         }
         }
         if segment.selectedSegmentIndex == 0 {
         if segment.selectedSegmentIndex == 0 {
             Utils.inTabChats = true
             Utils.inTabChats = true
-//            searchController.searchBar.placeholder = "Search chats & messages".localized()
-//            searchController.searchBar.searchTextField.attributedPlaceholder = NSAttributedString(string: "Search chats & messages".localized(), attributes: [NSAttributedString.Key.foregroundColor: UIColor.gray, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16)])
-        } else {
-//            searchController.searchBar.placeholder = "Search groups name".localized()
-//            searchController.searchBar.searchTextField.attributedPlaceholder = NSAttributedString(string: "Search groups name".localized(), attributes: [NSAttributedString.Key.foregroundColor: UIColor.gray, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16)])
         }
         }
-//        removeAllData()
-//        getData()
     }
     }
     
     
-//    func removeAllData() {
-//        groups.removeAll()
-//        groupMap.removeAll()
-//        chats.removeAll()
-//        tableView.reloadData()
-//    }
-    
     func textFieldDidChangeSelection(_ textField: UITextField) {
     func textFieldDidChangeSelection(_ textField: UITextField) {
         if textField == textViewSearch {
         if textField == textViewSearch {
             var textSerch = textField.text ?? ""
             var textSerch = textField.text ?? ""
@@ -777,29 +758,35 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
         }
         }
     }
     }
     
     
-    @objc func onReloadTab(notification: NSNotification) {
-        DispatchQueue.main.async {
-            self.getData()
+    private func reloadAllData() {
+        DispatchQueue.global().async { [self] in
+            if waitingLoading {
+                return
+            }
+            waitingLoading = true
+            while loadingData {
+                Thread.sleep(forTimeInterval: 0.5)
+            }
+            waitingLoading = false
+            getData()
         }
         }
     }
     }
     
     
+    @objc func onReloadTab(notification: NSNotification) {
+        reloadAllData()
+    }
+    
     @objc func onReload(notification: NSNotification) {
     @objc func onReload(notification: NSNotification) {
         let data:[AnyHashable : Any] = notification.userInfo!
         let data:[AnyHashable : Any] = notification.userInfo!
         if data["member"] as? String == User.getMyPin()! {
         if data["member"] as? String == User.getMyPin()! {
-            DispatchQueue.main.async {
-                self.getData()
-            }
+            reloadAllData()
         } else if data["state"] as? Int == 99 {
         } else if data["state"] as? Int == 99 {
-            DispatchQueue.main.async {
-                self.getData()
-            }
+            reloadAllData()
         }
         }
     }
     }
     
     
     @objc func onReceiveMessage(notification: NSNotification) {
     @objc func onReceiveMessage(notification: NSNotification) {
-        DispatchQueue.main.async {
-            self.getData()
-        }
+        reloadAllData()
     }
     }
     
     
     @objc func onStatusChat(notification: NSNotification) {
     @objc func onStatusChat(notification: NSNotification) {
@@ -890,6 +877,7 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
                 }
                 }
                 DispatchQueue.main.async {
                 DispatchQueue.main.async {
                     self.tableView.reloadData()
                     self.tableView.reloadData()
+                    self.loadingData = false
                 }
                 }
             }
             }
         }
         }
@@ -897,7 +885,41 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
     
     
     func getChats(completion: @escaping ()->()) {
     func getChats(completion: @escaping ()->()) {
         DispatchQueue.global().async {
         DispatchQueue.global().async {
-            self.chats = Chat.getData()
+            self.chatGroupMaps.removeAll()
+            let previousChat = self.chats
+            let allChats = Chat.getData()
+            var tempChats: [Chat] = []
+            for singleChat in allChats {
+                if !singleChat.groupId.isEmpty {
+                    let chatParentInPreviousChats = previousChat.first(where: { $0.isParent && $0.groupId == singleChat.groupId })
+                    if self.chatGroupMaps[singleChat.groupId] != nil {
+                        self.chatGroupMaps[singleChat.groupId]!.insert(singleChat, at: 0)
+                        if let parentChat = tempChats.first(where: { $0.groupId == singleChat.groupId && $0.isParent }) {
+                            let counterParent = parentChat.counter
+                            parentChat.counter = "\(Int(counterParent)! + Int(singleChat.counter)!)"
+                        }
+                        if let parentExist = chatParentInPreviousChats, parentExist.isSelected {
+                            if let indexParent = previousChat.firstIndex(where: { $0.isParent && $0.groupId == singleChat.groupId }){
+                                tempChats.insert(singleChat, at: indexParent + self.chatGroupMaps[singleChat.groupId]!.count)
+                            }
+                        }
+                    } else {
+                        self.chatGroupMaps[singleChat.groupId] = [singleChat]
+                        let parentChat = Chat(profile: singleChat.profile, groupName: singleChat.groupName, counter: singleChat.counter, groupId: singleChat.groupId)
+                        parentChat.isParent = true
+                        if let parentExist = chatParentInPreviousChats, parentExist.isSelected {
+                            parentChat.isSelected = true
+                            tempChats.append(parentChat)
+                            tempChats.append(singleChat)
+                        } else {
+                            tempChats.append(parentChat)
+                        }
+                    }
+                } else {
+                    tempChats.append(singleChat)
+                }
+            }
+            self.chats = tempChats
             completion()
             completion()
         }
         }
     }
     }
@@ -1066,6 +1088,10 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
             } else {
             } else {
                 data = chats[indexPath.row]
                 data = chats[indexPath.row]
             }
             }
+            if data.isParent {
+                expandCollapseChats(tableView: tableView, indexPath: indexPath)
+                return
+            }
             if let chooser = isChooser {
             if let chooser = isChooser {
                 if data.pin == "-999"{
                 if data.pin == "-999"{
                     return
                     return
@@ -1097,6 +1123,40 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
         }
         }
     }
     }
     
     
+    func expandCollapseChats(tableView: UITableView, indexPath: IndexPath) {
+        let data: Chat
+        if isFiltering {
+            data = fillteredData[indexPath.row] as! Chat
+        } else {
+            data = chats[indexPath.row]
+        }
+        data.isSelected = !data.isSelected
+        if data.isSelected {
+            for dataSubChat in self.chatGroupMaps[data.groupId]! {
+                if var indexParent = chats.firstIndex(where: { $0.isParent && $0.groupId == data.groupId }) {
+                    if isFiltering {
+                        fillteredData.insert(dataSubChat, at: indexParent + 1)
+                        indexParent+=1
+                    } else {
+                        chats.insert(dataSubChat, at: indexParent + 1)
+                        indexParent+=1
+                    }
+                }
+                
+            }
+        } else {
+            if isFiltering {
+                if var changedFillteredData = fillteredData as? [Chat] {
+                    changedFillteredData.removeAll(where: { $0.isParent == false && $0.groupId == data.groupId })
+                    self.fillteredData = changedFillteredData
+                }
+            } else {
+                chats.removeAll(where: { $0.isParent == false && $0.groupId == data.groupId })
+            }
+        }
+        tableView.reloadData()
+    }
+    
     func expandCollapseGroup(tableView: UITableView, indexPath: IndexPath) {
     func expandCollapseGroup(tableView: UITableView, indexPath: IndexPath) {
         let group: Group
         let group: Group
         if isFiltering {
         if isFiltering {
@@ -1217,17 +1277,12 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
             } else {
             } else {
                 getGroups(id: group.id) { g in
                 getGroups(id: group.id) { g in
                     DispatchQueue.main.async {
                     DispatchQueue.main.async {
-                        //print("index path section: \(indexPath.section)")
-                        //print("index path row: \(indexPath.row)")
-                        //print("index path item: \(indexPath.item)")
                         if self.isFiltering {
                         if self.isFiltering {
-//                                self.fillteredData.remove(at: indexPath.section)
                             if self.fillteredData[indexPath.section] is Group {
                             if self.fillteredData[indexPath.section] is Group {
                                 self.groupMap[(self.fillteredData[indexPath.section] as! Group).id] = 1
                                 self.groupMap[(self.fillteredData[indexPath.section] as! Group).id] = 1
                                 self.fillteredData.insert(contentsOf: g, at: indexPath.section + 1)
                                 self.fillteredData.insert(contentsOf: g, at: indexPath.section + 1)
                             }
                             }
                         } else {
                         } else {
-//                                self.groups.remove(at: indexPath.section)
                             self.groupMap[self.groups[indexPath.section].id] = 1
                             self.groupMap[self.groups[indexPath.section].id] = 1
                             self.groups.insert(contentsOf: g, at: indexPath.section + 1)
                             self.groups.insert(contentsOf: g, at: indexPath.section + 1)
                         }
                         }
@@ -1375,7 +1430,7 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
             }
             }
             if noData || (isFiltering && fillteredData.count == 0) {
             if noData || (isFiltering && fillteredData.count == 0) {
                 let labelNochat = UILabel()
                 let labelNochat = UILabel()
-                labelNochat.text = isFiltering ? "No Result".localized() : "There are no conversations".localized()
+                labelNochat.text = isFiltering ? "No Result".localized() : loadingData ? "Loading Data...".localized() : "There are no conversations".localized()
                 labelNochat.font = .systemFont(ofSize: 13)
                 labelNochat.font = .systemFont(ofSize: 13)
                 labelNochat.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
                 labelNochat.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
                 content.addSubview(labelNochat)
                 content.addSubview(labelNochat)
@@ -1390,7 +1445,7 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
             } else {
             } else {
                 if chats.count == 0 {
                 if chats.count == 0 {
                     let labelNochat = UILabel()
                     let labelNochat = UILabel()
-                    labelNochat.text = "There are no conversations".localized()
+                    labelNochat.text = loadingData ? "Loading Data...".localized() : "There are no conversations".localized()
                     labelNochat.font = .systemFont(ofSize: 13)
                     labelNochat.font = .systemFont(ofSize: 13)
                     labelNochat.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
                     labelNochat.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
                     content.addSubview(labelNochat)
                     content.addSubview(labelNochat)
@@ -1693,12 +1748,12 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
             content.addSubview(imageView)
             content.addSubview(imageView)
             imageView.translatesAutoresizingMaskIntoConstraints = false
             imageView.translatesAutoresizingMaskIntoConstraints = false
             NSLayoutConstraint.activate([
             NSLayoutConstraint.activate([
-                imageView.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 10.0),
                 imageView.topAnchor.constraint(equalTo: content.topAnchor, constant: 10.0),
                 imageView.topAnchor.constraint(equalTo: content.topAnchor, constant: 10.0),
                 imageView.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -10.0),
                 imageView.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -10.0),
                 imageView.widthAnchor.constraint(equalToConstant: 55.0),
                 imageView.widthAnchor.constraint(equalToConstant: 55.0),
                 imageView.heightAnchor.constraint(equalToConstant: 55.0)
                 imageView.heightAnchor.constraint(equalToConstant: 55.0)
             ])
             ])
+            var leadingAnchor = imageView.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 10.0)
             if data.profile.isEmpty && data.pin != "-999" && data.pin != "-997" {
             if data.profile.isEmpty && data.pin != "-999" && data.pin != "-997" {
                 if data.messageScope == "3" {
                 if data.messageScope == "3" {
                     imageView.image = UIImage(named: "Profile---Purple", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
                     imageView.image = UIImage(named: "Profile---Purple", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
@@ -1748,141 +1803,36 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
                         }
                         }
                     }
                     }
                 } else {
                 } else {
-                    getImage(name: data.profile, placeholderImage: UIImage(named: data.pin == "-999" ? "pb_button" : data.messageScope == "3" ? "Profile---Purple" : "Conversation---Purple", in: Bundle.resourceBundle(for: Nexilis.self), with: nil), isCircle: true, tableView: tableView, indexPath: indexPath, completion: { result, isDownloaded, image in
+                    if data.messageScope == "3" || data.isParent || data.pin == "-999" {
+                        getImage(name: data.profile, placeholderImage: UIImage(named: data.pin == "-999" ? "pb_button" : data.messageScope == "3" ? "Profile---Purple" : "Conversation---Purple", in: Bundle.resourceBundle(for: Nexilis.self), with: nil), isCircle: true, tableView: tableView, indexPath: indexPath, completion: { result, isDownloaded, image in
+                            imageView.image = image
+                        })
+                    } else {
+                        leadingAnchor = imageView.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 40.0)
+                        let image = UIImage(named: "Conversation---Purple", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
                         imageView.image = image
                         imageView.image = image
-                    })
+                    }
                 }
                 }
             }
             }
+            leadingAnchor.isActive = true
+            
             let titleView = UILabel()
             let titleView = UILabel()
             content.addSubview(titleView)
             content.addSubview(titleView)
             titleView.translatesAutoresizingMaskIntoConstraints = false
             titleView.translatesAutoresizingMaskIntoConstraints = false
             NSLayoutConstraint.activate([
             NSLayoutConstraint.activate([
                 titleView.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 10.0),
                 titleView.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 10.0),
-                titleView.topAnchor.constraint(equalTo: content.topAnchor, constant: 10.0),
                 titleView.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -40.0),
                 titleView.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -40.0),
             ])
             ])
-            titleView.text = data.name
             titleView.font = UIFont.systemFont(ofSize: 14, weight: .medium)
             titleView.font = UIFont.systemFont(ofSize: 14, weight: .medium)
             
             
-            let messageView = UILabel()
-            content.addSubview(messageView)
-            messageView.translatesAutoresizingMaskIntoConstraints = false
-            NSLayoutConstraint.activate([
-                messageView.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 10.0),
-                messageView.topAnchor.constraint(equalTo: titleView.bottomAnchor),
-                messageView.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -40.0),
-            ])
-            messageView.textColor = .gray
-            if data.messageText.contains("■") {
-                data.messageText = data.messageText.components(separatedBy: "■")[0]
-                data.messageText = data.messageText.trimmingCharacters(in: .whitespacesAndNewlines)
-            }
-            let text = Utils.previewMessageText(chat: data)
-            let idMe = User.getMyPin() as String?
-            if let attributeText = text as? NSMutableAttributedString {
-                let stringMessage = NSMutableAttributedString(string: "")
-                if data.fpin == idMe {
-                    if data.lock == "1" {
-                        if data.messageScope == "4" {
-                            stringMessage.append(NSAttributedString(string: "You".localized() + ": ", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12, weight: .medium)]))
-                        }
-                        stringMessage.append(("🚫 _"+"You were deleted this message".localized()+"_").richText())
-                    } else {
-                        let imageStatus = NSTextAttachment()
-                        let status = getRealStatus(messageId: data.messageId)
-                        if status == "0" {
-                            imageStatus.image = UIImage(systemName: "xmark.circle")!.withTintColor(UIColor.red, renderingMode: .alwaysOriginal)
-                        } else if status == "1" {
-                            imageStatus.image = UIImage(systemName: "clock.arrow.circlepath")!.withTintColor(UIColor.lightGray, renderingMode: .alwaysOriginal)
-                        } else if status == "2" {
-                            imageStatus.image = UIImage(named: "checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
-                        } else if (status == "3") {
-                            imageStatus.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
-                        } else if (status == "8") {
-                            imageStatus.image = UIImage(named: "message_status_ack", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
-                        } else {
-                            imageStatus.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.systemBlue)
-                        }
-                        imageStatus.bounds = CGRect(x: 0, y: -5, width: 15, height: 15)
-                        let imageStatusString = NSAttributedString(attachment: imageStatus)
-                        stringMessage.append(imageStatusString)
-                        stringMessage.append(NSAttributedString(string: " "))
-                        if data.messageScope == "4" {
-                            stringMessage.append(NSAttributedString(string: "You".localized() + ": ", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12, weight: .medium)]))
-                        }
-                        stringMessage.append(attributeText)
-                    }
-                } else {
-                    if data.messageScope == "4" {
-                        var fullname = User.getData(pin: data.fpin, lPin: data.pin)!.fullName
-                        let components = fullname.split(separator: " ")
-                        if components.count >= 2 {
-                            fullname = components.prefix(2).joined(separator: " ")
-                        }
-                        stringMessage.append(NSAttributedString(string: fullname + ": ", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12, weight: .medium)]))
-                    }
-                    if data.lock == "1" {
-                        stringMessage.append(("🚫 _"+"This message was deleted".localized()+"_").richText())
-                    } else {
-                        stringMessage.append(attributeText)
-                    }
-                }
-                messageView.attributedText = stringMessage
-            }
-            messageView.numberOfLines = 2
-            
             let timeView = UILabel()
             let timeView = UILabel()
-            content.addSubview(timeView)
-            timeView.translatesAutoresizingMaskIntoConstraints = false
-            NSLayoutConstraint.activate([
-                timeView.topAnchor.constraint(equalTo: content.topAnchor, constant: 10.0),
-                timeView.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -20.0),
-            ])
-            timeView.textColor = .gray
-            timeView.font = UIFont.systemFont(ofSize: 14)
-            
-            let date = Date(milliseconds: Int64(data.serverDate)!)
-            let calendar = Calendar.current
-            
-            if (calendar.isDateInToday(date)) {
-                let formatter = DateFormatter()
-                formatter.dateFormat = "HH:mm"
-                formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
-                timeView.text = formatter.string(from: date as Date)
-            } else {
-                let startOfNow = calendar.startOfDay(for: Date())
-                let startOfTimeStamp = calendar.startOfDay(for: date)
-                let components = calendar.dateComponents([.day], from: startOfNow, to: startOfTimeStamp)
-                let day = -(components.day!)
-                if day == 1 {
-                    timeView.text = "Yesterday".localized()
-                } else {
-                    if day < 7 {
-                        let formatter = DateFormatter()
-                        formatter.dateFormat = "EEEE"
-                        let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
-                        if lang == "id" {
-                            formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
-                        }
-                        timeView.text = formatter.string(from: date)
-                    } else {
-                        let formatter = DateFormatter()
-                        formatter.dateFormat = "M/dd/yy"
-                        formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
-                        let stringFormat = formatter.string(from: date as Date)
-                        timeView.text = stringFormat
-                    }
-                }
-            }
+            let viewCounter = UIView()
             
             
             if data.counter != "0" {
             if data.counter != "0" {
                 timeView.textColor = .systemRed
                 timeView.textColor = .systemRed
-                let viewCounter = UIView()
                 content.addSubview(viewCounter)
                 content.addSubview(viewCounter)
                 viewCounter.translatesAutoresizingMaskIntoConstraints = false
                 viewCounter.translatesAutoresizingMaskIntoConstraints = false
                 NSLayoutConstraint.activate([
                 NSLayoutConstraint.activate([
-                    viewCounter.topAnchor.constraint(equalTo: timeView.bottomAnchor, constant: 5.0),
-                    viewCounter.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -20),
                     viewCounter.widthAnchor.constraint(greaterThanOrEqualToConstant: 20),
                     viewCounter.widthAnchor.constraint(greaterThanOrEqualToConstant: 20),
                     viewCounter.heightAnchor.constraint(equalToConstant: 20)
                     viewCounter.heightAnchor.constraint(equalToConstant: 20)
                 ])
                 ])
@@ -1909,6 +1859,146 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
                 labelCounter.textColor = .secondaryColor
                 labelCounter.textColor = .secondaryColor
                 labelCounter.textAlignment = .center
                 labelCounter.textAlignment = .center
             }
             }
+            
+            if !data.isParent {
+                titleView.topAnchor.constraint(equalTo: content.topAnchor, constant: 10.0).isActive = true
+                titleView.text = data.name
+                
+                content.addSubview(timeView)
+                timeView.translatesAutoresizingMaskIntoConstraints = false
+                NSLayoutConstraint.activate([
+                    timeView.topAnchor.constraint(equalTo: content.topAnchor, constant: 10.0),
+                    timeView.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -20.0),
+                ])
+                timeView.textColor = .gray
+                timeView.font = UIFont.systemFont(ofSize: 14)
+                
+                let date = Date(milliseconds: Int64(data.serverDate) ?? 0)
+                let calendar = Calendar.current
+                
+                if (calendar.isDateInToday(date)) {
+                    let formatter = DateFormatter()
+                    formatter.dateFormat = "HH:mm"
+                    formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
+                    timeView.text = formatter.string(from: date as Date)
+                } else {
+                    let startOfNow = calendar.startOfDay(for: Date())
+                    let startOfTimeStamp = calendar.startOfDay(for: date)
+                    let components = calendar.dateComponents([.day], from: startOfNow, to: startOfTimeStamp)
+                    let day = -(components.day!)
+                    if day == 1 {
+                        timeView.text = "Yesterday".localized()
+                    } else {
+                        if day < 7 {
+                            let formatter = DateFormatter()
+                            formatter.dateFormat = "EEEE"
+                            let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
+                            if lang == "id" {
+                                formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
+                            }
+                            timeView.text = formatter.string(from: date)
+                        } else {
+                            let formatter = DateFormatter()
+                            formatter.dateFormat = "M/dd/yy"
+                            formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
+                            let stringFormat = formatter.string(from: date as Date)
+                            timeView.text = stringFormat
+                        }
+                    }
+                }
+                
+                let messageView = UILabel()
+                content.addSubview(messageView)
+                messageView.translatesAutoresizingMaskIntoConstraints = false
+                NSLayoutConstraint.activate([
+                    messageView.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 10.0),
+                    messageView.topAnchor.constraint(equalTo: titleView.bottomAnchor),
+                    messageView.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -40.0),
+                ])
+                messageView.textColor = .gray
+                if data.messageText.contains("■") {
+                    data.messageText = data.messageText.components(separatedBy: "■")[0]
+                    data.messageText = data.messageText.trimmingCharacters(in: .whitespacesAndNewlines)
+                }
+                let text = Utils.previewMessageText(chat: data)
+                let idMe = User.getMyPin() as String?
+                if let attributeText = text as? NSMutableAttributedString {
+                    let stringMessage = NSMutableAttributedString(string: "")
+                    if data.fpin == idMe {
+                        if data.lock == "1" {
+                            if data.messageScope == "4" {
+                                stringMessage.append(NSAttributedString(string: "You".localized() + ": ", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12, weight: .medium)]))
+                            }
+                            stringMessage.append(("🚫 _"+"You were deleted this message".localized()+"_").richText())
+                        } else {
+                            let imageStatus = NSTextAttachment()
+                            let status = getRealStatus(messageId: data.messageId)
+                            if status == "0" {
+                                imageStatus.image = UIImage(systemName: "xmark.circle")!.withTintColor(UIColor.red, renderingMode: .alwaysOriginal)
+                            } else if status == "1" {
+                                imageStatus.image = UIImage(systemName: "clock.arrow.circlepath")!.withTintColor(UIColor.lightGray, renderingMode: .alwaysOriginal)
+                            } else if status == "2" {
+                                imageStatus.image = UIImage(named: "checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
+                            } else if (status == "3") {
+                                imageStatus.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
+                            } else if (status == "8") {
+                                imageStatus.image = UIImage(named: "message_status_ack", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
+                            } else {
+                                imageStatus.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.systemBlue)
+                            }
+                            imageStatus.bounds = CGRect(x: 0, y: -5, width: 15, height: 15)
+                            let imageStatusString = NSAttributedString(attachment: imageStatus)
+                            stringMessage.append(imageStatusString)
+                            stringMessage.append(NSAttributedString(string: " "))
+                            if data.messageScope == "4" {
+                                stringMessage.append(NSAttributedString(string: "You".localized() + ": ", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12, weight: .medium)]))
+                            }
+                            stringMessage.append(attributeText)
+                        }
+                    } else {
+                        if data.messageScope == "4" {
+                            var fullname = User.getData(pin: data.fpin, lPin: data.pin)!.fullName
+                            let components = fullname.split(separator: " ")
+                            if components.count >= 2 {
+                                fullname = components.prefix(2).joined(separator: " ")
+                            }
+                            stringMessage.append(NSAttributedString(string: fullname + ": ", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12, weight: .medium)]))
+                        }
+                        if data.lock == "1" {
+                            stringMessage.append(("🚫 _"+"This message was deleted".localized()+"_").richText())
+                        } else {
+                            stringMessage.append(attributeText)
+                        }
+                    }
+                    messageView.attributedText = stringMessage
+                }
+                messageView.numberOfLines = 2
+                
+                if data.counter != "0" {
+                    viewCounter.topAnchor.constraint(equalTo: timeView.bottomAnchor, constant: 5.0).isActive = true
+                    viewCounter.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -20).isActive = true
+                }
+            } else {
+                titleView.centerYAnchor.constraint(equalTo: content.centerYAnchor).isActive = true
+                titleView.text = data.groupName
+                
+                let iconName = (data.isSelected) ? "chevron.up.circle" : "chevron.down.circle"
+                let imageView = UIImageView(image: UIImage(systemName: iconName))
+                imageView.tintColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
+                content.addSubview(imageView)
+                imageView.translatesAutoresizingMaskIntoConstraints = false
+                NSLayoutConstraint.activate([
+                    imageView.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -20),
+                    imageView.centerYAnchor.constraint(equalTo: content.centerYAnchor),
+                    imageView.widthAnchor.constraint(equalToConstant: 20),
+                    imageView.heightAnchor.constraint(equalToConstant: 20)
+                ])
+                
+                if data.counter != "0" {
+                    viewCounter.trailingAnchor.constraint(equalTo: imageView.leadingAnchor, constant: -5).isActive = true
+                    viewCounter.centerYAnchor.constraint(equalTo: content.centerYAnchor).isActive = true
+                }
+            }
         case 1:
         case 1:
             cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifierGroup", for: indexPath)
             cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifierGroup", for: indexPath)
             var content = cell.defaultContentConfiguration()
             var content = cell.defaultContentConfiguration()

+ 2 - 2
NexilisLite/NexilisLite.podspec

@@ -8,7 +8,7 @@
 
 
 Pod::Spec.new do |spec|
 Pod::Spec.new do |spec|
   spec.name         = "NexilisLite"
   spec.name         = "NexilisLite"
-  spec.version      = "3.3.1"
+  spec.version      = "5.0.5"
   spec.summary      = "NexilisLite Framework"
   spec.summary      = "NexilisLite Framework"
   spec.description  = <<-DESC
   spec.description  = <<-DESC
   NexilisLite Framework, embed Contact Center, Live Streaming, Push Notifications, Instant Messaging, Video and VoIP Calling features into your mobile apps within minutes...
   NexilisLite Framework, embed Contact Center, Live Streaming, Push Notifications, Instant Messaging, Video and VoIP Calling features into your mobile apps within minutes...
@@ -24,7 +24,7 @@ Pod::Spec.new do |spec|
   spec.resource_bundles = { 'NexilisLite' => ['NexilisLite/Resource/**/*']}
   spec.resource_bundles = { 'NexilisLite' => ['NexilisLite/Resource/**/*']}
   spec.swift_version = '5.5.1'
   spec.swift_version = '5.5.1'
   spec.dependency 'FMDB', '~> 2.7.12'
   spec.dependency 'FMDB', '~> 2.7.12'
-  spec.dependency 'nuSDKService', '~> 4.0.3'
+  spec.dependency 'nuSDKService', '~> 4.0.4'
   spec.dependency 'NotificationBannerSwift', '~> 3.1.0'
   spec.dependency 'NotificationBannerSwift', '~> 3.1.0'
   spec.dependency 'Alamofire', '~> 5.10.1'
   spec.dependency 'Alamofire', '~> 5.10.1'
   spec.dependency 'SDWebImage', '~> 5.20.0'
   spec.dependency 'SDWebImage', '~> 5.20.0'

+ 29 - 5
NexilisLite/NexilisLite/Source/APIS.swift

@@ -427,6 +427,21 @@ public class APIS: NSObject {
         }
         }
     }
     }
     
     
+    public static func openSecureBrowser() {
+        let isChangeProfile = Utils.getSetProfile()
+        if !isChangeProfile {
+            APIS.showChangeProfile()
+            return
+        }
+        let controller = BNIBookingWebView()
+        controller.isSecureBrowser = true
+        if UIApplication.shared.visibleViewController?.navigationController != nil {
+            UIApplication.shared.visibleViewController?.navigationController?.present(controller, animated: true, completion: nil)
+        } else {
+            UIApplication.shared.visibleViewController?.present(controller, animated: true, completion: nil)
+        }
+    }
+    
     public static func openCreateGroup() {
     public static func openCreateGroup() {
         let isChangeProfile = Utils.getSetProfile()
         let isChangeProfile = Utils.getSetProfile()
         if !isChangeProfile {
         if !isChangeProfile {
@@ -1228,14 +1243,23 @@ public class APIS: NSObject {
 //        } catch {
 //        } catch {
 //        }
 //        }
 //        exit(0)
 //        exit(0)
+        UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum)
     }
     }
     
     
     public static func enterForeground() {
     public static func enterForeground() {
-//        do {
-//            try API.switchCBI(cbiI: Callback(), bLight: false)
-//        } catch {
-//        }
-//        setDataForShareExtension()
+        do {
+            if !Nexilis.sAPIKey.isEmpty {
+                print("MASUK initConnection enterForeground")
+                var id = Utils.getConnectionID()
+                if id.isEmpty {
+                    let sDID = UIDevice.current.identifierForVendor?.uuidString ?? "UNK-DEVICE"
+                    id = String(sDID[sDID.index(sDID.endIndex, offsetBy: -5)...])
+                    Utils.setConnectionID(value: id)
+                }
+                try API.initConnection(sAPIK: Nexilis.sAPIKey, cbiI: Callback(), sTCPAddr: Nexilis.ADDRESS, nTCPPort: Nexilis.PORT, sUserID: id, sStartWH: "09:00")
+            }
+        } catch {
+        }
         checkDataForShareExtension()
         checkDataForShareExtension()
         UIApplication.shared.applicationIconBadgeNumber = 0
         UIApplication.shared.applicationIconBadgeNumber = 0
         UNUserNotificationCenter.current().removeAllDeliveredNotifications()
         UNUserNotificationCenter.current().removeAllDeliveredNotifications()

+ 9 - 3
NexilisLite/NexilisLite/Source/IncomingThread.swift

@@ -1299,13 +1299,19 @@ class IncomingThread {
         } else {
         } else {
             Nexilis.saveMessage(message: message, withStatus: false)
             Nexilis.saveMessage(message: message, withStatus: false)
         }
         }
-        DispatchQueue.main.async {
-            if APIS.checkAppStateisBackground() {
+        UNUserNotificationCenter.current().getPendingNotificationRequests{ notificationsPending in
+            let identifier = message.getBody(key : CoreMessage_TMessageKey.MESSAGE_ID, default_value : "")
+            let matchingNotifications = notificationsPending.filter { $0.identifier == identifier }
+            if matchingNotifications.isEmpty {
                 UNUserNotificationCenter.current().getDeliveredNotifications { notifications in
                 UNUserNotificationCenter.current().getDeliveredNotifications { notifications in
                     let identifier = message.getBody(key : CoreMessage_TMessageKey.MESSAGE_ID, default_value : "")
                     let identifier = message.getBody(key : CoreMessage_TMessageKey.MESSAGE_ID, default_value : "")
                     let matchingNotifications = notifications.filter { $0.request.identifier == identifier }
                     let matchingNotifications = notifications.filter { $0.request.identifier == identifier }
                     if matchingNotifications.isEmpty {
                     if matchingNotifications.isEmpty {
-                        APIS.addNotificationNexilis(message)
+                        DispatchQueue.main.async {
+                            if APIS.checkAppStateisBackground() {
+                                APIS.addNotificationNexilis(message)
+                            }
+                        }
                     }
                     }
                 }
                 }
             }
             }

+ 53 - 8
NexilisLite/NexilisLite/Source/Model/Chat.swift

@@ -12,7 +12,7 @@ public class Chat: Model {
     public let fpin: String
     public let fpin: String
     public let pin: String
     public let pin: String
     public let messageId: String
     public let messageId: String
-    public let counter: String
+    public var counter: String
     public var messageText: String
     public var messageText: String
     public let serverDate: String
     public let serverDate: String
     public let image: String
     public let image: String
@@ -29,6 +29,10 @@ public class Chat: Model {
     public let thumb: String
     public let thumb: String
     public let audio: String
     public let audio: String
     public let gif: String
     public let gif: String
+    public let groupId: String
+    public let groupName: String
+    public var isSelected: Bool
+    public var isParent: Bool
     
     
     public init(pin: String) {
     public init(pin: String) {
         self.fpin = ""
         self.fpin = ""
@@ -51,9 +55,40 @@ public class Chat: Model {
         self.thumb = ""
         self.thumb = ""
         self.audio = ""
         self.audio = ""
         self.gif = ""
         self.gif = ""
+        self.groupId = ""
+        self.groupName = ""
+        self.isSelected = false
+        self.isParent = false
     }
     }
     
     
-    public init(fpin:String, pin: String, messageId: String, counter: String, messageText: String, serverDate: String, image: String, video: String, file: String, attachmentFlag: String, messageScope: String, name: String, profile: String, official: String, status: String, credential: String, lock: String, thumb: String = "", audio: String = "", gif: String = "") {
+    public init(profile: String, groupName: String, counter: String, groupId: String) {
+        self.fpin = ""
+        self.pin = ""
+        self.messageId = ""
+        self.counter = counter
+        self.messageText = ""
+        self.serverDate = ""
+        self.image = ""
+        self.video = ""
+        self.file = ""
+        self.attachmentFlag = ""
+        self.messageScope = ""
+        self.name = ""
+        self.profile = profile
+        self.official = ""
+        self.status = ""
+        self.credential = ""
+        self.lock = ""
+        self.thumb = ""
+        self.audio = ""
+        self.gif = ""
+        self.groupId = groupId
+        self.groupName = groupName
+        self.isSelected = false
+        self.isParent = false
+    }
+    
+    public init(fpin:String, pin: String, messageId: String, counter: String, messageText: String, serverDate: String, image: String, video: String, file: String, attachmentFlag: String, messageScope: String, name: String, profile: String, official: String, status: String, credential: String, lock: String, thumb: String = "", audio: String = "", gif: String = "", groupId: String = "", groupName: String = "", isSelected: Bool = false, isParent: Bool = false) {
         self.fpin = fpin
         self.fpin = fpin
         self.pin = pin
         self.pin = pin
         self.messageId = messageId
         self.messageId = messageId
@@ -74,12 +109,20 @@ public class Chat: Model {
         self.thumb = thumb
         self.thumb = thumb
         self.audio = audio
         self.audio = audio
         self.gif = gif
         self.gif = gif
+        self.groupId = groupId
+        self.groupName = groupName
+        self.isSelected = isSelected
+        self.isParent = isParent
     }
     }
     
     
     public static func == (lhs: Chat, rhs: Chat) -> Bool {
     public static func == (lhs: Chat, rhs: Chat) -> Bool {
         return lhs.pin == rhs.pin
         return lhs.pin == rhs.pin
     }
     }
     
     
+    public func copy() -> Chat {
+        return Chat(fpin: self.fpin, pin: self.pin, messageId: self.messageId, counter: self.counter, messageText: self.messageText, serverDate: self.serverDate, image: self.image, video: self.video, file: self.file, attachmentFlag: self.attachmentFlag, messageScope: self.messageScope, name: self.name, profile: self.profile, official: self.official, status: self.status, credential: self.credential, lock: self.lock, thumb: self.thumb, audio: self.audio, gif: self.gif, groupId: self.groupId, groupName: self.groupName, isSelected: self.isSelected, isParent: self.isParent)
+    }
+    
     public var description: String {
     public var description: String {
         return ""
         return ""
     }
     }
@@ -135,15 +178,15 @@ public class Chat: Model {
                     lastQuery = "m.audio_id IS NOT NULL AND m.audio_id != ''"
                     lastQuery = "m.audio_id IS NOT NULL AND m.audio_id != ''"
                 }
                 }
                 var query = """
                 var query = """
-                            select m.f_pin, ms.l_pin, ms.message_id, ms.counter, m.message_text, m.server_date, m.image_id, m.video_id, m.file_id, m.attachment_flag, m.message_scope_id, b.first_name || ' ' || ifnull(b.last_name, '') name, b.image_id profile, b.official_account, m.status, m.credential, m.lock, m.audio_id, m.gif_id from MESSAGE_SUMMARY ms, MESSAGE m, BUDDY b where ms.l_pin = b.f_pin and ms.message_id = m.message_id \(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'") and m.is_call_center = 0
+                            select m.f_pin, ms.l_pin, ms.message_id, ms.counter, m.message_text, m.server_date, m.image_id, m.video_id, m.file_id, m.attachment_flag, m.message_scope_id, b.first_name || ' ' || ifnull(b.last_name, '') name, b.image_id profile, b.official_account, m.status, m.credential, m.lock, m.audio_id, m.gif_id, '' group_id, '' group_name from MESSAGE_SUMMARY ms, MESSAGE m, BUDDY b where ms.l_pin = b.f_pin and ms.message_id = m.message_id \(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'") and m.is_call_center = 0
                             union
                             union
-                            select m.f_pin, ms.l_pin, ms.message_id, ms.counter, m.message_text, m.server_date, m.image_id, m.video_id, m.file_id, m.attachment_flag, m.message_scope_id, 'Bot' name, '' profile, '', m.status, m.credential, m.lock, m.audio_id, m.gif_id from MESSAGE_SUMMARY ms, MESSAGE m where ms.l_pin = '-999' and ms.message_id = m.message_id\(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'")
+                            select m.f_pin, ms.l_pin, ms.message_id, ms.counter, m.message_text, m.server_date, m.image_id, m.video_id, m.file_id, m.attachment_flag, m.message_scope_id, 'Bot' name, '' profile, '', m.status, m.credential, m.lock, m.audio_id, m.gif_id, '' group_id, '' group_name from MESSAGE_SUMMARY ms, MESSAGE m where ms.l_pin = '-999' and ms.message_id = m.message_id\(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'")
                             union
                             union
-                            select m.f_pin, ms.l_pin, ms.message_id, ms.counter, m.message_text, m.server_date, m.image_id, m.video_id, m.file_id, m.attachment_flag, m.message_scope_id, 'GPT SmartBot' name, '' profile, '', m.status, m.credential, m.lock, m.audio_id, m.gif_id from MESSAGE_SUMMARY ms, MESSAGE m where ms.l_pin = '-997' and ms.message_id = m.message_id\(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'")
+                            select m.f_pin, ms.l_pin, ms.message_id, ms.counter, m.message_text, m.server_date, m.image_id, m.video_id, m.file_id, m.attachment_flag, m.message_scope_id, 'GPT SmartBot' name, '' profile, '', m.status, m.credential, m.lock, m.audio_id, m.gif_id, '' group_id, '' group_name from MESSAGE_SUMMARY ms, MESSAGE m where ms.l_pin = '-997' and ms.message_id = m.message_id\(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'")
                             union
                             union
-                            select m.f_pin, ms.l_pin, ms.message_id, ms.counter, m.message_text, m.server_date, m.image_id, m.video_id, m.file_id, m.attachment_flag, m.message_scope_id, b.f_name || ' (\("Lounge".localized()))', b.image_id profile, b.official, m.status, m.credential, m.lock, m.audio_id, m.gif_id from MESSAGE_SUMMARY ms, MESSAGE m, GROUPZ b where ms.l_pin = b.group_id and ms.message_id = m.message_id \(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'") and m.is_call_center = 0
+                            select m.f_pin, ms.l_pin, ms.message_id, ms.counter, m.message_text, m.server_date, m.image_id, m.video_id, m.file_id, m.attachment_flag, m.message_scope_id, '\("Lounge".localized())' name, b.image_id profile, b.official, m.status, m.credential, m.lock, m.audio_id, m.gif_id, b.group_id, b.f_name group_name from MESSAGE_SUMMARY ms, MESSAGE m, GROUPZ b where ms.l_pin = b.group_id and ms.message_id = m.message_id \(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'") and m.is_call_center = 0
                             union
                             union
-                            select m.f_pin, ms.l_pin, ms.message_id, ms.counter, m.message_text, m.server_date, m.image_id, m.video_id, m.file_id, m.attachment_flag, m.message_scope_id, c.f_name || ' (' || b.title || ')', c.image_id profile, '', m.status, m.credential, m.lock, m.audio_id, m.gif_id from MESSAGE_SUMMARY ms, MESSAGE m, DISCUSSION_FORUM b, GROUPZ c where b.group_id = c.group_id and ms.l_pin = b.chat_id and ms.message_id = m.message_id \(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'") and m.is_call_center = 0
+                            select m.f_pin, ms.l_pin, ms.message_id, ms.counter, m.message_text, m.server_date, m.image_id, m.video_id, m.file_id, m.attachment_flag, m.message_scope_id, b.title, c.image_id profile, '', m.status, m.credential, m.lock, m.audio_id, m.gif_id, c.group_id, c.f_name group_name from MESSAGE_SUMMARY ms, MESSAGE m, DISCUSSION_FORUM b, GROUPZ c where b.group_id = c.group_id and ms.l_pin = b.chat_id and ms.message_id = m.message_id \(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'") and m.is_call_center = 0
                             order by 6 desc
                             order by 6 desc
                             """
                             """
                 if !lastQuery.isEmpty {
                 if !lastQuery.isEmpty {
@@ -182,7 +225,9 @@ public class Chat: Model {
                                         lock: !lastQuery.isEmpty ? cursorData.string(forColumnIndex: 12) ?? "" : cursorData.string(forColumnIndex: 16) ?? "",
                                         lock: !lastQuery.isEmpty ? cursorData.string(forColumnIndex: 12) ?? "" : cursorData.string(forColumnIndex: 16) ?? "",
                                         thumb: !lastQuery.isEmpty ? cursorData.string(forColumnIndex: 3) ?? "" : "",
                                         thumb: !lastQuery.isEmpty ? cursorData.string(forColumnIndex: 3) ?? "" : "",
                                         audio: !lastQuery.isEmpty ? cursorData.string(forColumnIndex: 13) ?? "" : cursorData.string(forColumnIndex: 17) ?? "",
                                         audio: !lastQuery.isEmpty ? cursorData.string(forColumnIndex: 13) ?? "" : cursorData.string(forColumnIndex: 17) ?? "",
-                                        gif: !lastQuery.isEmpty ? cursorData.string(forColumnIndex: 17) ?? "" : cursorData.string(forColumnIndex: 18) ?? "")
+                                        gif: !lastQuery.isEmpty ? cursorData.string(forColumnIndex: 17) ?? "" : cursorData.string(forColumnIndex: 18) ?? "",
+                                        groupId: cursorData.string(forColumnIndex: 19) ?? "",
+                                        groupName: cursorData.string(forColumnIndex: 20) ?? "")
                         chats.append(chat)
                         chats.append(chat)
                     }
                     }
                     cursorData.close()
                     cursorData.close()

+ 156 - 113
NexilisLite/NexilisLite/Source/Nexilis.swift

@@ -120,6 +120,7 @@ public class Nexilis: NSObject {
     public static let IDX_SELF_ACT = 100
     public static let IDX_SELF_ACT = 100
     public static let IDX_SOCIAL_COMMERCE = 101
     public static let IDX_SOCIAL_COMMERCE = 101
     public static let IDX_NEWS = 102
     public static let IDX_NEWS = 102
+    public static let IDX_SECURE_BROWSER = 105
     
     
     public static var callAPNActivated = false
     public static var callAPNActivated = false
     
     
@@ -164,6 +165,8 @@ public class Nexilis: NSObject {
         imageCache.countLimit = 100
         imageCache.countLimit = 100
         imageCache.totalCostLimit = 1024 * 1024 * 200
         imageCache.totalCostLimit = 1024 * 1024 * 200
         
         
+        UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum)
+        
         DispatchQueue.global().async {
         DispatchQueue.global().async {
             do {
             do {
                 if Utils.getFinishInitPrefsr() {
                 if Utils.getFinishInitPrefsr() {
@@ -181,7 +184,7 @@ public class Nexilis: NSObject {
                     id = String(sDID[sDID.index(sDID.endIndex, offsetBy: -5)...])
                     id = String(sDID[sDID.index(sDID.endIndex, offsetBy: -5)...])
                     Utils.setConnectionID(value: id)
                     Utils.setConnectionID(value: id)
                 }
                 }
-                try API.initConnection(bSwitchIP: false, sAPIK: apiKey, aAppMain: nil, cbiI: Callback(), sTCPAddr: Nexilis.ADDRESS, nTCPPort: Nexilis.PORT, sUserID: id, sStartWH: "09:00")
+                try API.initConnection(sAPIK: apiKey, cbiI: Callback(), sTCPAddr: Nexilis.ADDRESS, nTCPPort: Nexilis.PORT, sUserID: id, sStartWH: "09:00")
                 while (API.nGetCLXConnState() == 0) {
                 while (API.nGetCLXConnState() == 0) {
                     Thread.sleep(forTimeInterval: 0.5)
                     Thread.sleep(forTimeInterval: 0.5)
                 }
                 }
@@ -297,41 +300,46 @@ public class Nexilis: NSObject {
         }
         }
         
         
         _ = LocationManager()
         _ = LocationManager()
-        
-        initiateSoundCall()
-        
-        //print("MANIA \(UIFont.systemFont(ofSize: 12)) <> \(UIFont.italicSystemFont(ofSize: 12)) <> \(UIFont.boldSystemFont(ofSize: 12))")
     }
     }
     
     
-    private static func initiateSoundCall() {
+    public static func playRingtoneCall() {
+        var ringtonePath = Bundle.resourceBundle(for: Nexilis.self).url(forResource: "pb_call_in", withExtension: "mp3")
+        if ringtonePath == nil {
+            ringtonePath = Bundle.resourcesMediaBundle(for: Nexilis.self).url(forResource: "pb_call_in", withExtension: "mp3")
+        }
         do {
         do {
-            guard let ringtonePath = Bundle.resourceBundle(for: Nexilis.self).url(forResource: "pb_call_in", withExtension: "mp3") else {
-                return
-            }
-//            print("ringtone ada")
-            
-            ringtonePlayer = try AVAudioPlayer(contentsOf:ringtonePath)
+            ringtonePlayer = try AVAudioPlayer(contentsOf:ringtonePath!)
             ringtonePlayer?.numberOfLoops = -1
             ringtonePlayer?.numberOfLoops = -1
             ringtonePlayer?.prepareToPlay()
             ringtonePlayer?.prepareToPlay()
+            ringtonePlayer?.play()
+        } catch {
             
             
-//            print("END INITIATE ringtone")
-            
-            guard let ringbacktonePath = Bundle.resourceBundle(for: Nexilis.self).url(forResource: "pb_call_out", withExtension: "mp3") else {
-                return
-            }
-//            print("ringbacktone ada")
-            
-            ringbacktonePlayer = try AVAudioPlayer(contentsOf:ringbacktonePath)
+        }
+    }
+    
+    public static func stopRingtoneCall() {
+        ringtonePlayer?.stop()
+    }
+    
+    public static func playRingbacktoneCall() {
+        var ringbacktonePath = Bundle.resourceBundle(for: Nexilis.self).url(forResource: "pb_call_out", withExtension: "mp3")
+        if ringbacktonePath == nil {
+            ringbacktonePath = Bundle.resourcesMediaBundle(for: Nexilis.self).url(forResource: "pb_call_out", withExtension: "mp3")
+        }
+        do {
+            ringbacktonePlayer = try AVAudioPlayer(contentsOf:ringbacktonePath!)
             ringbacktonePlayer?.numberOfLoops = -1
             ringbacktonePlayer?.numberOfLoops = -1
             ringbacktonePlayer?.prepareToPlay()
             ringbacktonePlayer?.prepareToPlay()
-            
-//            print("END INITIATE ringbacktone")
-            
+            ringbacktonePlayer?.play()
         } catch {
         } catch {
             
             
         }
         }
     }
     }
     
     
+    public static func stopRingbacktoneCall() {
+        ringbacktonePlayer?.stop()
+    }
+    
     public static func addFB(viewController: UIViewController, fromMAB: Bool) {
     public static func addFB(viewController: UIViewController, fromMAB: Bool) {
         if let keyWindow = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) {
         if let keyWindow = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) {
             keyWindow.addSubview(floatingButton)
             keyWindow.addSubview(floatingButton)
@@ -629,23 +637,23 @@ public class Nexilis: NSObject {
         SecureUserDefaults.shared.removeValue(forKey: "waitingRequestCC")
         SecureUserDefaults.shared.removeValue(forKey: "waitingRequestCC")
     }
     }
     
     
-    public static func changeUser(f_pin: String){
-        do {
-            //print("change user to fpin")
-            Nexilis.dispatch = DispatchGroup()
-            Nexilis.dispatch?.enter()
-            
-            try API.switchUser(cbiI: Callback(), sUserID: f_pin)
-            
-            // wait until connection true
-            Nexilis.dispatch?.wait()
-            Nexilis.dispatch = nil
-            //print("success change user to fpin")
-//            _ = Nexilis.write(message: CoreMessage_TMessageBank.getChangeConnectionID(p_pin: f_pin))
-        } catch{
-            //print(error)
-        }
-    }
+//    public static func changeUser(f_pin: String){
+//        do {
+//            //print("change user to fpin")
+//            Nexilis.dispatch = DispatchGroup()
+//            Nexilis.dispatch?.enter()
+//            
+//            try API.switchUser(cbiI: Callback(), sUserID: f_pin)
+//            
+//            // wait until connection true
+//            Nexilis.dispatch?.wait()
+//            Nexilis.dispatch = nil
+//            //print("success change user to fpin")
+////            _ = Nexilis.write(message: CoreMessage_TMessageBank.getChangeConnectionID(p_pin: f_pin))
+//        } catch{
+//            //print(error)
+//        }
+//    }
     
     
     public static func apiSendChat(destination: String, message: String, isGroup: Bool, thumbnailName: String = "", imageName: String = "", videoName: String = "", fileName: String = "", audioName: String = "", replyMessageId : String = "") -> String {
     public static func apiSendChat(destination: String, message: String, isGroup: Bool, thumbnailName: String = "", imageName: String = "", videoName: String = "", fileName: String = "", audioName: String = "", replyMessageId : String = "") -> String {
         let message = CoreMessage_TMessageBank.sendMessage(l_pin: destination, message_scope_id: isGroup ? "4" : "3", status: "3", message_text: message, credential: "", attachment_flag: !imageName.isEmpty ? "1" : !videoName.isEmpty ? "2" : !audioName.isEmpty ? "5" : !fileName.isEmpty ? "6" : "0", ex_blog_id: "", message_large_text: "", ex_format: "", image_id: imageName, audio_id: audioName, video_id: videoName, file_id: fileName, thumb_id: thumbnailName, reff_id: replyMessageId, read_receipts: "4", chat_id: "", is_call_center: "0", call_center_id: "", opposite_pin: User.getMyPin() ?? "")
         let message = CoreMessage_TMessageBank.sendMessage(l_pin: destination, message_scope_id: isGroup ? "4" : "3", status: "3", message_text: message, credential: "", attachment_flag: !imageName.isEmpty ? "1" : !videoName.isEmpty ? "2" : !audioName.isEmpty ? "5" : !fileName.isEmpty ? "6" : "0", ex_blog_id: "", message_large_text: "", ex_format: "", image_id: imageName, audio_id: audioName, video_id: videoName, file_id: fileName, thumb_id: thumbnailName, reff_id: replyMessageId, read_receipts: "4", chat_id: "", is_call_center: "0", call_center_id: "", opposite_pin: User.getMyPin() ?? "")
@@ -839,7 +847,7 @@ public class Nexilis: NSObject {
         if HTTPCookieStorage.shared.cookies(for: URL(string: Utils.getDomainOpr())!)!.count == 0 && !Utils.getCookiesMobileForStorage().isEmpty {
         if HTTPCookieStorage.shared.cookies(for: URL(string: Utils.getDomainOpr())!)!.count == 0 && !Utils.getCookiesMobileForStorage().isEmpty {
             HTTPCookieStorage.shared.setCookies(convertJSONStringToCookies(jsonString: Utils.getCookiesMobileForStorage()), for: url, mainDocumentURL: nil)
             HTTPCookieStorage.shared.setCookies(convertJSONStringToCookies(jsonString: Utils.getCookiesMobileForStorage()), for: url, mainDocumentURL: nil)
         }
         }
-        print("[App] getAddress:", result)
+//        print("[App] getAddress:", result)
         return result
         return result
     }
     }
     
     
@@ -1097,6 +1105,8 @@ public class Nexilis: NSObject {
             APIS.openFavoriteMessage()
             APIS.openFavoriteMessage()
         } else if index == IDX_SECURE_FOLDER {
         } else if index == IDX_SECURE_FOLDER {
             APIS.openSecureFolder()
             APIS.openSecureFolder()
+        } else if index == IDX_SECURE_BROWSER {
+            APIS.openSecureBrowser()
         } else {
         } else {
             openApp(id: id)
             openApp(id: id)
         }
         }
@@ -3819,89 +3829,122 @@ extension Nexilis: MessageDelegate {
                     subtitle.textColor = .white
                     subtitle.textColor = .white
                     
                     
                     if floating != nil {
                     if floating != nil {
-                        floating.dismiss()
+                        return
                     }
                     }
-                    floating = FloatingNotificationBanner(customView: container)
-                    floating.bannerHeight = 100.0
-                    floating.transparency = 0.9
                     
                     
-                    if threadIdentifier == "-999" {
-                        if !Utils.getIconDock().isEmpty {
-                            let dataImage = try? Data(contentsOf: URL(string: Utils.getUrlDock()!)!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
-                            if dataImage != nil {
-                                profileImage.image = UIImage(data: dataImage!)
-                            }
-                        } else {
-                            profileImage.image = UIImage(named: "pb_button", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
-                        }
-                    } else 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
+                    displayNotif()
+                    
+                    func displayNotif() {
+                        floating = FloatingNotificationBanner(customView: container)
+                        floating.bannerHeight = 100.0
+                        floating.transparency = 0.9
+                        
+                        if threadIdentifier == "-999" {
+                            if !Utils.getIconDock().isEmpty {
+                                let dataImage = try? Data(contentsOf: URL(string: Utils.getUrlDock()!)!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
+                                if dataImage != nil {
+                                    profileImage.image = UIImage(data: dataImage!)
+                                }
                             } else {
                             } else {
-                                Download().startHTTP(forKey: profile) { (name, progress) in
-                                    guard progress == 100 else {
-                                        return
-                                    }
-                                    
-                                    DispatchQueue.main.async { [self] in
-                                        profileImage.image = UIImage(contentsOfFile: file.path)
-                                        profileImage.backgroundColor = .clear
-                                        if !onGoingCC.isEmpty {
-                                            floating.autoDismiss = false
+                                profileImage.image = UIImage(named: "pb_button", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
+                            }
+                        } else 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 {
+                                    Download().startHTTP(forKey: profile) { (name, progress) in
+                                        guard progress == 100 else {
+                                            return
                                         }
                                         }
-                                        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)
-                                        floating.onTap = {
-                                            showNotif()
+                                        
+                                        DispatchQueue.main.async { [self] in
+                                            profileImage.image = UIImage(contentsOfFile: file.path)
+                                            profileImage.backgroundColor = .clear
+                                            if !onGoingCC.isEmpty {
+                                                floating.autoDismiss = false
+                                            }
+                                            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)
+                                            floating.onTap = {
+                                                self.floating = nil
+                                                showNotif()
+                                            }
+                                            var soundId: String = SecureUserDefaults.shared.value(forKey: "newNotifSoundPersonal") ?? "001:Nexilis Message (Default)"
+                                            if message.getBody(key: CoreMessage_TMessageKey.MESSAGE_SCOPE_ID) == "4" {
+                                                soundId = SecureUserDefaults.shared.value(forKey: "newNotifSoundGroup") ?? "001:Nexilis Message (Default)"
+                                            }
+                                            do {
+                                                var nameSound = soundId.components(separatedBy: ":")[1].replacingOccurrences(of: " ", with: "_")
+                                                if nameSound.contains("_(Default)") {
+                                                    nameSound = nameSound.replacingOccurrences(of: "_(Default)", with: "")
+                                                }
+                                                var soundURL = Bundle.resourceBundle(for: Nexilis.self).url(forResource: nameSound, withExtension: "mp3")
+                                                if soundURL == nil {
+                                                    soundURL = Bundle.resourcesMediaBundle(for: Nexilis.self).url(forResource: nameSound, withExtension: "mp3")
+                                                }
+                                                Nexilis.sharedAudioPlayer = try AVAudioPlayer(contentsOf: soundURL!)
+                                                Nexilis.sharedAudioPlayer?.prepareToPlay()
+                                                Nexilis.sharedAudioPlayer?.play()
+                                                DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: {
+                                                    self.floating = nil
+                                                })
+                                            } catch {
+                                                
+                                            }
                                         }
                                         }
                                     }
                                     }
+                                    return
                                 }
                                 }
-                                return
-                            }
-                        } catch {}
-                        profileImage.contentMode = .scaleAspectFill
-                    } else {
-                        profileImage.circle()
-                        if message.getBody(key: messageScopeId) == "3" {
-                            profileImage.image = UIImage(systemName: "person")
+                            } catch {}
+                            profileImage.contentMode = .scaleAspectFill
                         } else {
                         } else {
-                            profileImage.image = UIImage(systemName: "person.3")
+                            profileImage.circle()
+                            if message.getBody(key: messageScopeId) == "3" {
+                                profileImage.image = UIImage(systemName: "person")
+                            } else {
+                                profileImage.image = UIImage(systemName: "person.3")
+                            }
+                            profileImage.contentMode = .scaleAspectFit
+                            profileImage.backgroundColor = .lightGray
+                            profileImage.tintColor = .white
                         }
                         }
-                        profileImage.contentMode = .scaleAspectFit
-                        profileImage.backgroundColor = .lightGray
-                        profileImage.tintColor = .white
-                    }
-                    
-                    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)
-//                    let vibrateMode: Bool = SecureUserDefaults.shared.value(forKey: "vibrateMode") ?? false
-                    var soundId: String = SecureUserDefaults.shared.value(forKey: "newNotifSoundPersonal") ?? "001:Nexilis Message (Default)"
-                    if message.getBody(key: CoreMessage_TMessageKey.MESSAGE_SCOPE_ID) == "4" {
-                        soundId = SecureUserDefaults.shared.value(forKey: "newNotifSoundGroup") ?? "001:Nexilis Message (Default)"
-                    }
-                    do {
-                        var nameSound = soundId.components(separatedBy: ":")[1].replacingOccurrences(of: " ", with: "_")
-                        if nameSound.contains("_(Default)") {
-                            nameSound = nameSound.replacingOccurrences(of: "_(Default)", with: "")
+                        
+                        print("SHOW KAH?0")
+                        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)
+    //                    let vibrateMode: Bool = SecureUserDefaults.shared.value(forKey: "vibrateMode") ?? false
+                        var soundId: String = SecureUserDefaults.shared.value(forKey: "newNotifSoundPersonal") ?? "001:Nexilis Message (Default)"
+                        if message.getBody(key: CoreMessage_TMessageKey.MESSAGE_SCOPE_ID) == "4" {
+                            soundId = SecureUserDefaults.shared.value(forKey: "newNotifSoundGroup") ?? "001:Nexilis Message (Default)"
+                        }
+                        do {
+                            var nameSound = soundId.components(separatedBy: ":")[1].replacingOccurrences(of: " ", with: "_")
+                            if nameSound.contains("_(Default)") {
+                                nameSound = nameSound.replacingOccurrences(of: "_(Default)", with: "")
+                            }
+                            var soundURL = Bundle.resourceBundle(for: Nexilis.self).url(forResource: nameSound, withExtension: "mp3")
+                            if soundURL == nil {
+                                soundURL = Bundle.resourcesMediaBundle(for: Nexilis.self).url(forResource: nameSound, withExtension: "mp3")
+                            }
+                            Nexilis.sharedAudioPlayer = try AVAudioPlayer(contentsOf: soundURL!)
+                            Nexilis.sharedAudioPlayer?.prepareToPlay()
+                            Nexilis.sharedAudioPlayer?.play()
+                        } catch {
+                            
                         }
                         }
-                        var soundURL = Bundle.resourceBundle(for: Nexilis.self).url(forResource: nameSound, withExtension: "mp3")
-                        if soundURL == nil {
-                            soundURL = Bundle.resourcesMediaBundle(for: Nexilis.self).url(forResource: nameSound, withExtension: "mp3")
+                        DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: {
+                            self.floating = nil
+                        })
+//                        if !onGoingCC.isEmpty {
+//                            floating.autoDismiss = false
+//                        }
+                        floating.onTap = {
+                            self.floating = nil
+                            showNotif()
                         }
                         }
-                        Nexilis.sharedAudioPlayer = try AVAudioPlayer(contentsOf: soundURL!)
-                        Nexilis.sharedAudioPlayer?.prepareToPlay()
-                        Nexilis.sharedAudioPlayer?.play()
-                    } catch {
-                        
-                    }
-                    if !onGoingCC.isEmpty {
-                        floating.autoDismiss = false
-                    }
-                    floating.onTap = {
-                        showNotif()
                     }
                     }
                     func showNotif() {
                     func showNotif() {
                         if message.getBody(key: attachmentFlag) == "59" {
                         if message.getBody(key: attachmentFlag) == "59" {

+ 54 - 2
NexilisLite/NexilisLite/Source/View/BNIView/BNIBookingWebView.swift

@@ -15,6 +15,8 @@ public class BNIBookingWebView: UIViewController, WKNavigationDelegate, UIScroll
     var webView = WKWebView()
     var webView = WKWebView()
     let closeButton = UIButton()
     let closeButton = UIButton()
     public var customUrl = ""
     public var customUrl = ""
+    public var isSecureBrowser = false
+    let textField = UITextField()
     
     
     var isAllowSpeech = false
     var isAllowSpeech = false
     
     
@@ -44,9 +46,46 @@ public class BNIBookingWebView: UIViewController, WKNavigationDelegate, UIScroll
         configuration.applicationNameForUserAgent = finalUserAgent
         configuration.applicationNameForUserAgent = finalUserAgent
         webView = WKWebView(frame: .zero, configuration: configuration)
         webView = WKWebView(frame: .zero, configuration: configuration)
         
         
+        let containerView = UIView()
+        containerView.backgroundColor = .white
+        if isSecureBrowser {
+            view.addSubview(containerView)
+            containerView.translatesAutoresizingMaskIntoConstraints = false
+            containerView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
+            containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
+            containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
+            containerView.heightAnchor.constraint(equalToConstant: 44).isActive = true
+            
+            containerView.addSubview(textField)
+            textField.translatesAutoresizingMaskIntoConstraints = false
+            textField.leftAnchor.constraint(equalTo: containerView.leftAnchor, constant: 10.0).isActive = true
+            textField.centerYAnchor.constraint(equalTo: containerView.centerYAnchor).isActive = true
+            textField.heightAnchor.constraint(equalToConstant: 40).isActive = true
+            textField.widthAnchor.constraint(equalToConstant: view.bounds.size.width - 80).isActive = true
+            textField.layer.borderColor = UIColor.lightGray.cgColor
+            textField.layer.borderWidth = 1.0
+            textField.layer.cornerRadius = 5.0
+            textField.clipsToBounds = true
+            
+            let buttonGo = UIButton(type: .custom)
+            buttonGo.setTitle("Go".localized(), for: .normal)
+            buttonGo.setTitleColor(.black, for: .normal)
+            buttonGo.addTarget(self, action: #selector(goAction), for: .touchUpInside)
+            containerView.addSubview(buttonGo)
+            buttonGo.translatesAutoresizingMaskIntoConstraints = false
+            buttonGo.leftAnchor.constraint(equalTo: textField.rightAnchor, constant: 10.0).isActive = true
+            buttonGo.rightAnchor.constraint(equalTo: containerView.rightAnchor, constant: -10.0).isActive = true
+            buttonGo.centerYAnchor.constraint(equalTo: containerView.centerYAnchor).isActive = true
+            buttonGo.heightAnchor.constraint(equalToConstant: 40).isActive = true
+        }
+        
         view.addSubview(webView)
         view.addSubview(webView)
         webView.translatesAutoresizingMaskIntoConstraints = false
         webView.translatesAutoresizingMaskIntoConstraints = false
-        webView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
+        if isSecureBrowser {
+            webView.topAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true
+        } else {
+            webView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
+        }
         webView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
         webView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
         webView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
         webView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
         webView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
         webView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
@@ -115,7 +154,20 @@ public class BNIBookingWebView: UIViewController, WKNavigationDelegate, UIScroll
         }
         }
         stringQMS = stringQMS + "&lang=\(intLang)&theme=\(self.traitCollection.userInterfaceStyle == .dark ? "0" : "1")"
         stringQMS = stringQMS + "&lang=\(intLang)&theme=\(self.traitCollection.userInterfaceStyle == .dark ? "0" : "1")"
         let url = URL(string: "\(stringQMS)")!
         let url = URL(string: "\(stringQMS)")!
-        loadURLWithCookie(url: url)
+        if !isSecureBrowser {
+            loadURLWithCookie(url: url)
+        }
+    }
+    
+    @objc func goAction() {
+        if let text = textField.text, !text.isEmpty {
+            var urlString = text
+            if !text.starts(with: "www.") && !text.starts(with: "https://") {
+                urlString = "https://www.google.com/search?q=\(text)"
+            }
+            let url = URL(string: urlString)!
+            loadURLWithCookie(url: url)
+        }
     }
     }
     
     
     func loadURLWithCookie(url: URL) {
     func loadURLWithCookie(url: URL) {

+ 26 - 26
NexilisLite/NexilisLite/Source/View/Chat/EditorGroup.swift

@@ -4560,7 +4560,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
         messageText.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
         messageText.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
         messageText.font = .systemFont(ofSize: 12)
         messageText.font = .systemFont(ofSize: 12)
         
         
-        var textChat = (dataMessages[indexPath.row]["message_text"])! as? String
+        var textChat = dataMessages[indexPath.row]["message_text"] as? String ?? ""
         let originalMessageText = textChat
         let originalMessageText = textChat
         if (dataMessages[indexPath.row]["lock"] != nil && (dataMessages[indexPath.row]["lock"])! as? String == "1") {
         if (dataMessages[indexPath.row]["lock"] != nil && (dataMessages[indexPath.row]["lock"])! as? String == "1") {
             if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
             if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
@@ -4575,14 +4575,14 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
         }
         }
         
         
         if !audioChat.isEmpty {
         if !audioChat.isEmpty {
-            textChat = textChat!.components(separatedBy: "|")[0]
+            textChat = textChat.components(separatedBy: "|")[0]
         }
         }
         
         
         let imageSticker = UIImageView()
         let imageSticker = UIImageView()
         
         
         if let attachmentFlag = dataMessages[indexPath.row]["attachment_flag"], let attachmentFlag = attachmentFlag as? String {
         if let attachmentFlag = dataMessages[indexPath.row]["attachment_flag"], let attachmentFlag = attachmentFlag as? String {
-            if attachmentFlag == "27" || attachmentFlag == "26", let data = textChat { // live streaming
-                if let json = try! JSONSerialization.jsonObject(with: data.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
+            if attachmentFlag == "27" || attachmentFlag == "26" { // live streaming
+                if let json = try! JSONSerialization.jsonObject(with: textChat.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
                     Database().database?.inTransaction({ fmdb, rollback in
                     Database().database?.inTransaction({ fmdb, rollback in
                         let title = json["title"]  as? String ?? ""
                         let title = json["title"]  as? String ?? ""
                         let description = json["description"]  as? String ?? ""
                         let description = json["description"]  as? String ?? ""
@@ -4619,41 +4619,41 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
                 imageSticker.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
                 imageSticker.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
                 imageSticker.bottomAnchor.constraint(equalTo: messageText.topAnchor, constant: -5).isActive = true
                 imageSticker.bottomAnchor.constraint(equalTo: messageText.topAnchor, constant: -5).isActive = true
                 imageSticker.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
                 imageSticker.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
-                var imageStickerBundle = UIImage(named: (textChat!.components(separatedBy: "/")[1]), in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
+                var imageStickerBundle = UIImage(named: (textChat.components(separatedBy: "/")[1]), in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
                 if imageStickerBundle == nil {
                 if imageStickerBundle == nil {
-                    imageStickerBundle = UIImage(named: (textChat!.components(separatedBy: "/")[1]), in: Bundle.resourcesMediaBundle(for: Nexilis.self), with: nil)
+                    imageStickerBundle = UIImage(named: (textChat.components(separatedBy: "/")[1]), in: Bundle.resourcesMediaBundle(for: Nexilis.self), with: nil)
                 }
                 }
                 imageSticker.image = imageStickerBundle //resourcesMediaBundle
                 imageSticker.image = imageStickerBundle //resourcesMediaBundle
                 imageSticker.contentMode = .scaleAspectFit
                 imageSticker.contentMode = .scaleAspectFit
             }
             }
             else {
             else {
-                messageText.attributedText = textChat!.richText(group_id: self.dataGroup["group_id"]  as? String ?? "")
+                messageText.attributedText = textChat.richText(group_id: self.dataGroup["group_id"]  as? String ?? "")
                 modifyText()
                 modifyText()
             }
             }
         } else {
         } else {
-            messageText.attributedText = textChat!.richText(group_id: self.dataGroup["group_id"]  as? String ?? "")
+            messageText.attributedText = textChat.richText(group_id: self.dataGroup["group_id"]  as? String ?? "")
             modifyText()
             modifyText()
         }
         }
         
         
         func modifyText() {
         func modifyText() {
-            if !textChat!.isEmpty {
-                if textChat!.contains("■"){
-                    textChat = textChat!.components(separatedBy: "■")[0]
-                    textChat = textChat!.trimmingCharacters(in: .whitespacesAndNewlines)
+            if !textChat.isEmpty {
+                if textChat.contains("■"){
+                    textChat = textChat.components(separatedBy: "■")[0]
+                    textChat = textChat.trimmingCharacters(in: .whitespacesAndNewlines)
                 }
                 }
                 if !fileChat.isEmpty {
                 if !fileChat.isEmpty {
-                    textChat = textChat!.components(separatedBy: "|")[1]
+                    textChat = textChat.components(separatedBy: "|")[1]
                 }
                 }
-                let finalAtribute = textChat!.richText(group_id: self.dataGroup["group_id"]  as? String ?? "")
+                let finalAtribute = textChat.richText(group_id: self.dataGroup["group_id"]  as? String ?? "")
                 textChat = finalAtribute.string
                 textChat = finalAtribute.string
                 let urlPattern = "(https?://|www\\.)\\S+"
                 let urlPattern = "(https?://|www\\.)\\S+"
                 if let regex = try? NSRegularExpression(pattern: urlPattern, options: []) {
                 if let regex = try? NSRegularExpression(pattern: urlPattern, options: []) {
-                    let matches = regex.matches(in: textChat!, options: [], range: NSRange(textChat!.startIndex..., in: textChat!))
+                    let matches = regex.matches(in: textChat, options: [], range: NSRange(textChat.startIndex..., in: textChat))
                     
                     
                     for match in matches {
                     for match in matches {
-                        if let range = Range(match.range, in: textChat!) {
-                            let linkText = String(textChat![range])
-                            let nsRange = NSRange(range, in: textChat!)
+                        if let range = Range(match.range, in: textChat) {
+                            let linkText = String(textChat[range])
+                            let nsRange = NSRange(range, in: textChat)
                             finalAtribute.addAttribute(.link, value: linkText, range: nsRange)
                             finalAtribute.addAttribute(.link, value: linkText, range: nsRange)
                             finalAtribute.addAttribute(.foregroundColor, value: UIColor.blue, range: nsRange)
                             finalAtribute.addAttribute(.foregroundColor, value: UIColor.blue, range: nsRange)
                             finalAtribute.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: nsRange)
                             finalAtribute.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: nsRange)
@@ -4672,8 +4672,8 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
         }
         }
         
         
         if isSearching && textSearch.count > 1 {
         if isSearching && textSearch.count > 1 {
-            messageText.attributedText = textChat!.richText(isSearching: true, textSearch: textSearch, group_id: self.dataGroup["group_id"]  as? String ?? "")
-            if textChat!.lowercased().contains(textSearch) {
+            messageText.attributedText = textChat.richText(isSearching: true, textSearch: textSearch, group_id: self.dataGroup["group_id"]  as? String ?? "")
+            if textChat.lowercased().contains(textSearch) {
                 countMatchesSearch += 1
                 countMatchesSearch += 1
             }
             }
         }
         }
@@ -5008,12 +5008,12 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
             let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
             let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
             let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
             let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
             let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
             let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
-            let arrExtFile = (originalMessageText!.components(separatedBy: "|")[0]).split(separator: ".")
+            let arrExtFile = (originalMessageText.components(separatedBy: "|")[0]).split(separator: ".")
             let finalExtFile = arrExtFile[arrExtFile.count - 1]
             let finalExtFile = arrExtFile[arrExtFile.count - 1]
             if let dirPath = paths.first {
             if let dirPath = paths.first {
                 let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(fileChat)
                 let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(fileChat)
                 if FileManager.default.fileExists(atPath: fileURL.path) {
                 if FileManager.default.fileExists(atPath: fileURL.path) {
-                    if let dataFile = try? Data(contentsOf: fileURL), textChat!.isEmpty {
+                    if let dataFile = try? Data(contentsOf: fileURL), textChat.isEmpty {
                         var sizeOfFile = Int(dataFile.count / 1000000)
                         var sizeOfFile = Int(dataFile.count / 1000000)
                         if (sizeOfFile < 1) {
                         if (sizeOfFile < 1) {
                             sizeOfFile = Int(dataFile.count / 1000)
                             sizeOfFile = Int(dataFile.count / 1000)
@@ -5033,7 +5033,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
                 }
                 }
                 else if FileEncryption.shared.isSecureExists(filename: fileChat) {
                 else if FileEncryption.shared.isSecureExists(filename: fileChat) {
                     do {
                     do {
-                        if let dataFile = try FileEncryption.shared.readSecure(filename: fileChat), textChat!.isEmpty {
+                        if let dataFile = try FileEncryption.shared.readSecure(filename: fileChat), textChat.isEmpty {
                             var sizeOfFile = Int(dataFile.count / 1000000)
                             var sizeOfFile = Int(dataFile.count / 1000000)
                             if (sizeOfFile < 1) {
                             if (sizeOfFile < 1) {
                                 sizeOfFile = Int(dataFile.count / 1000)
                                 sizeOfFile = Int(dataFile.count / 1000)
@@ -5088,7 +5088,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
             nameFile.widthAnchor.constraint(lessThanOrEqualToConstant: 200).isActive = true
             nameFile.widthAnchor.constraint(lessThanOrEqualToConstant: 200).isActive = true
             nameFile.font = UIFont.systemFont(ofSize: 12, weight: .medium)
             nameFile.font = UIFont.systemFont(ofSize: 12, weight: .medium)
             nameFile.textColor = .white
             nameFile.textColor = .white
-            nameFile.text = originalMessageText?.components(separatedBy: "|")[0]
+            nameFile.text = originalMessageText.components(separatedBy: "|")[0]
             
             
             if (dataMessages[indexPath.row]["progress"] as! Double != 100.0) {
             if (dataMessages[indexPath.row]["progress"] as! Double != 100.0) {
                 let containerLoading = UIView()
                 let containerLoading = UIView()
@@ -5139,9 +5139,9 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
         
         
         let containerLinkMessage = UIView()
         let containerLinkMessage = UIView()
         var isLoadingShowLink = false
         var isLoadingShowLink = false
-        if thumbChat.isEmpty && fileChat.isEmpty && !textChat!.isEmpty {
+        if thumbChat.isEmpty && fileChat.isEmpty && !textChat.isEmpty {
             var text = ""
             var text = ""
-            let listTextSplitBreak = textChat!.components(separatedBy: "\n")
+            let listTextSplitBreak = textChat.components(separatedBy: "\n")
             let indexFirstLinkSplitBreak = listTextSplitBreak.firstIndex(where: { $0.contains("www.") || $0.contains("http://") || $0.contains("https://") })
             let indexFirstLinkSplitBreak = listTextSplitBreak.firstIndex(where: { $0.contains("www.") || $0.contains("http://") || $0.contains("https://") })
             if indexFirstLinkSplitBreak != nil {
             if indexFirstLinkSplitBreak != nil {
                 let listTextSplitSpace = listTextSplitBreak[indexFirstLinkSplitBreak!].components(separatedBy: " ")
                 let listTextSplitSpace = listTextSplitBreak[indexFirstLinkSplitBreak!].components(separatedBy: " ")

+ 256 - 136
NexilisLite/NexilisLite/Source/View/Control/ContactChatViewController.swift

@@ -23,6 +23,8 @@ class ContactChatViewController: UITableViewController {
     
     
     var chats: [Chat] = []
     var chats: [Chat] = []
     
     
+    var chatGroupMaps: [String: [Chat]] = [:]
+    
     var contacts: [User] = []
     var contacts: [User] = []
     
     
     var groups: [Group] = []
     var groups: [Group] = []
@@ -45,6 +47,9 @@ class ContactChatViewController: UITableViewController {
     
     
     var noData = false
     var noData = false
     
     
+    var loadingData = true
+    var waitingLoading = false
+    
     var noUCList = false
     var noUCList = false
     
     
     func filterContentForSearchText(_ searchText: String) {
     func filterContentForSearchText(_ searchText: String) {
@@ -64,7 +69,12 @@ class ContactChatViewController: UITableViewController {
                 case 2:
                 case 2:
                     fillteredData = self.groups.filter { $0.name.lowercased().contains(searchText.lowercased()) }
                     fillteredData = self.groups.filter { $0.name.lowercased().contains(searchText.lowercased()) }
                 default:
                 default:
-                    fillteredData = self.chats.filter { $0.name.lowercased().contains(searchText.lowercased()) || $0.messageText.lowercased().contains(searchText.lowercased()) }
+                    var group_id: String?
+                    if let filterGroupKey = self.chatGroupMaps.first(where: { $0.value.contains { $0.name.lowercased().contains(searchText.lowercased()) || $0.groupName.lowercased().contains(searchText.lowercased()) || $0.messageText.lowercased().contains(searchText.lowercased()) } } ) {
+                        group_id = filterGroupKey.key
+                    }
+                    let deepCopyChats = self.chats.map{ $0.copy() }
+                    fillteredData = deepCopyChats.filter { $0.name.lowercased().contains(searchText.lowercased()) || $0.messageText.lowercased().contains(searchText.lowercased()) || $0.groupId == group_id }
                 }
                 }
             } else {
             } else {
                 switch segment.selectedSegmentIndex {
                 switch segment.selectedSegmentIndex {
@@ -254,30 +264,35 @@ class ContactChatViewController: UITableViewController {
 //        tableView.reloadData()
 //        tableView.reloadData()
 //    }
 //    }
     
     
-    @objc func onReload(notification: NSNotification) {
-        let data:[AnyHashable : Any] = notification.userInfo!
-        if data["member"] as? String == User.getMyPin() {
-            DispatchQueue.main.async {
-                self.getData()
+    private func reloadAllData() {
+        DispatchQueue.global().async { [self] in
+            if waitingLoading {
+                return
             }
             }
-        } else if data["state"] as? Int == 99 {
-            //print("MASUK 99")
-            DispatchQueue.main.async {
-                self.getData()
+            waitingLoading = true
+            while loadingData {
+                Thread.sleep(forTimeInterval: 0.5)
             }
             }
+            waitingLoading = false
+            getData()
         }
         }
     }
     }
     
     
     @objc func onReloadTab(notification: NSNotification) {
     @objc func onReloadTab(notification: NSNotification) {
-        DispatchQueue.main.async {
-            self.getData()
+        reloadAllData()
+    }
+    
+    @objc func onReload(notification: NSNotification) {
+        let data:[AnyHashable : Any] = notification.userInfo!
+        if data["member"] as? String == User.getMyPin()! {
+            reloadAllData()
+        } else if data["state"] as? Int == 99 {
+            reloadAllData()
         }
         }
     }
     }
     
     
     @objc func onReceiveMessage(notification: NSNotification) {
     @objc func onReceiveMessage(notification: NSNotification) {
-        DispatchQueue.main.async {
-            self.getData()
-        }
+        reloadAllData()
     }
     }
     
     
     @objc func onStatusChat(notification: NSNotification) {
     @objc func onStatusChat(notification: NSNotification) {
@@ -411,7 +426,41 @@ class ContactChatViewController: UITableViewController {
     
     
     func getChats(completion: @escaping ()->()) {
     func getChats(completion: @escaping ()->()) {
         DispatchQueue.global().async {
         DispatchQueue.global().async {
-            self.chats = Chat.getData()
+            self.chatGroupMaps.removeAll()
+            let previousChat = self.chats
+            let allChats = Chat.getData()
+            var tempChats: [Chat] = []
+            for singleChat in allChats {
+                if !singleChat.groupId.isEmpty {
+                    let chatParentInPreviousChats = previousChat.first(where: { $0.isParent && $0.groupId == singleChat.groupId })
+                    if self.chatGroupMaps[singleChat.groupId] != nil {
+                        self.chatGroupMaps[singleChat.groupId]!.insert(singleChat, at: 0)
+                        if let parentChat = tempChats.first(where: { $0.groupId == singleChat.groupId && $0.isParent }) {
+                            let counterParent = parentChat.counter
+                            parentChat.counter = "\(Int(counterParent)! + Int(singleChat.counter)!)"
+                        }
+                        if let parentExist = chatParentInPreviousChats, parentExist.isSelected {
+                            if let indexParent = previousChat.firstIndex(where: { $0.isParent && $0.groupId == singleChat.groupId }){
+                                tempChats.insert(singleChat, at: indexParent + self.chatGroupMaps[singleChat.groupId]!.count)
+                            }
+                        }
+                    } else {
+                        self.chatGroupMaps[singleChat.groupId] = [singleChat]
+                        let parentChat = Chat(profile: singleChat.profile, groupName: singleChat.groupName, counter: singleChat.counter, groupId: singleChat.groupId)
+                        parentChat.isParent = true
+                        if let parentExist = chatParentInPreviousChats, parentExist.isSelected {
+                            parentChat.isSelected = true
+                            tempChats.append(parentChat)
+                            tempChats.append(singleChat)
+                        } else {
+                            tempChats.append(parentChat)
+                        }
+                    }
+                } else {
+                    tempChats.append(singleChat)
+                }
+            }
+            self.chats = tempChats
             completion()
             completion()
         }
         }
     }
     }
@@ -654,6 +703,10 @@ extension ContactChatViewController {
                 } else {
                 } else {
                     data = chats[indexPath.row]
                     data = chats[indexPath.row]
                 }
                 }
+                if data.isParent {
+                    expandCollapseChats(tableView: tableView, indexPath: indexPath)
+                    return
+                }
                 if let chooser = isChooser {
                 if let chooser = isChooser {
                     var exblock = User.getDataCanNil(pin: data.pin)?.ex_block
                     var exblock = User.getDataCanNil(pin: data.pin)?.ex_block
                     exblock = exblock == nil ? "0" : exblock!.isEmpty ? "0" : exblock!
                     exblock = exblock == nil ? "0" : exblock!.isEmpty ? "0" : exblock!
@@ -716,6 +769,40 @@ extension ContactChatViewController {
         }
         }
     }
     }
     
     
+    func expandCollapseChats(tableView: UITableView, indexPath: IndexPath) {
+        let data: Chat
+        if isFilltering {
+            data = fillteredData[indexPath.row] as! Chat
+        } else {
+            data = chats[indexPath.row]
+        }
+        data.isSelected = !data.isSelected
+        if data.isSelected {
+            for dataSubChat in self.chatGroupMaps[data.groupId]! {
+                if var indexParent = chats.firstIndex(where: { $0.isParent && $0.groupId == data.groupId }) {
+                    if isFilltering {
+                        fillteredData.insert(dataSubChat, at: indexParent + 1)
+                        indexParent+=1
+                    } else {
+                        chats.insert(dataSubChat, at: indexParent + 1)
+                        indexParent+=1
+                    }
+                }
+                
+            }
+        } else {
+            if isFilltering {
+                if var changedFillteredData = fillteredData as? [Chat] {
+                    changedFillteredData.removeAll(where: { $0.isParent == false && $0.groupId == data.groupId })
+                    self.fillteredData = changedFillteredData
+                }
+            } else {
+                chats.removeAll(where: { $0.isParent == false && $0.groupId == data.groupId })
+            }
+        }
+        tableView.reloadData()
+    }
+    
     func expandCollapseGroup(tableView: UITableView, indexPath: IndexPath) {
     func expandCollapseGroup(tableView: UITableView, indexPath: IndexPath) {
         let group: Group
         let group: Group
         if isFilltering {
         if isFilltering {
@@ -831,21 +918,15 @@ extension ContactChatViewController {
             } else {
             } else {
                 getGroups(id: group.id) { g in
                 getGroups(id: group.id) { g in
                     DispatchQueue.main.async {
                     DispatchQueue.main.async {
-                        //print("index path section: \(indexPath.section)")
-                        //print("index path row: \(indexPath.row)")
-                        //print("index path item: \(indexPath.item)")
                         if self.isFilltering {
                         if self.isFilltering {
-//                                self.fillteredData.remove(at: indexPath.section)
                             if self.fillteredData[indexPath.section] is Group {
                             if self.fillteredData[indexPath.section] is Group {
                                 self.groupMap[(self.fillteredData[indexPath.section] as! Group).id] = 1
                                 self.groupMap[(self.fillteredData[indexPath.section] as! Group).id] = 1
                                 self.fillteredData.insert(contentsOf: g, at: indexPath.section + 1)
                                 self.fillteredData.insert(contentsOf: g, at: indexPath.section + 1)
                             }
                             }
                         } else {
                         } else {
-//                                self.groups.remove(at: indexPath.section)
                             self.groupMap[self.groups[indexPath.section].id] = 1
                             self.groupMap[self.groups[indexPath.section].id] = 1
                             self.groups.insert(contentsOf: g, at: indexPath.section + 1)
                             self.groups.insert(contentsOf: g, at: indexPath.section + 1)
                         }
                         }
-                        //print("groupMap: \(self.groupMap)")
                         tableView.reloadData()
                         tableView.reloadData()
                         
                         
                         self.expandCollapseGroup(tableView: tableView, indexPath: IndexPath(row: 0, section: indexPath.section + 1))
                         self.expandCollapseGroup(tableView: tableView, indexPath: IndexPath(row: 0, section: indexPath.section + 1))
@@ -1116,12 +1197,12 @@ extension ContactChatViewController {
                 content.addSubview(imageView)
                 content.addSubview(imageView)
                 imageView.translatesAutoresizingMaskIntoConstraints = false
                 imageView.translatesAutoresizingMaskIntoConstraints = false
                 NSLayoutConstraint.activate([
                 NSLayoutConstraint.activate([
-                    imageView.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 10.0),
                     imageView.topAnchor.constraint(equalTo: content.topAnchor, constant: 10.0),
                     imageView.topAnchor.constraint(equalTo: content.topAnchor, constant: 10.0),
                     imageView.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -10.0),
                     imageView.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -10.0),
                     imageView.widthAnchor.constraint(equalToConstant: 55.0),
                     imageView.widthAnchor.constraint(equalToConstant: 55.0),
                     imageView.heightAnchor.constraint(equalToConstant: 55.0)
                     imageView.heightAnchor.constraint(equalToConstant: 55.0)
                 ])
                 ])
+                var leadingAnchor = imageView.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 10.0)
                 if data.profile.isEmpty && data.pin != "-999" && data.pin != "-997" {
                 if data.profile.isEmpty && data.pin != "-999" && data.pin != "-997" {
                     if data.messageScope == "3" {
                     if data.messageScope == "3" {
                         imageView.image = UIImage(named: "Profile---Purple", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
                         imageView.image = UIImage(named: "Profile---Purple", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
@@ -1131,7 +1212,7 @@ extension ContactChatViewController {
                 } else if data.pin == "-997" {
                 } else if data.pin == "-997" {
                     imageView.frame = CGRect(x: 0, y: 0, width: 55.0, height: 55.0)
                     imageView.frame = CGRect(x: 0, y: 0, width: 55.0, height: 55.0)
                     imageView.circle()
                     imageView.circle()
-                    if let urlGif = Bundle.resourceBundle(for: Nexilis.self).url(forResource: "pb_gpt_bot", withExtension: "gif") {//resourcesMediaBundle
+                    if let urlGif = Bundle.resourceBundle(for: Nexilis.self).url(forResource: "pb_gpt_bot", withExtension: "gif") {
                         imageView.sd_setImage(with: urlGif) { (image, error, cacheType, imageURL) in
                         imageView.sd_setImage(with: urlGif) { (image, error, cacheType, imageURL) in
                             if error == nil {
                             if error == nil {
                                 imageView.animationImages = image?.images
                                 imageView.animationImages = image?.images
@@ -1171,136 +1252,36 @@ extension ContactChatViewController {
                             }
                             }
                         }
                         }
                     } else {
                     } else {
-                        getImage(name: data.profile, placeholderImage: UIImage(named: data.pin == "-999" ? "pb_button" : data.messageScope == "3" ? "Profile---Purple" : "Conversation---Purple", in: Bundle.resourceBundle(for: Nexilis.self), with: nil), isCircle: true, tableView: tableView, indexPath: indexPath, completion: { result, isDownloaded, image in
+                        if data.messageScope == "3" || data.isParent || data.pin == "-999" {
+                            getImage(name: data.profile, placeholderImage: UIImage(named: data.pin == "-999" ? "pb_button" : data.messageScope == "3" ? "Profile---Purple" : "Conversation---Purple", in: Bundle.resourceBundle(for: Nexilis.self), with: nil), isCircle: true, tableView: tableView, indexPath: indexPath, completion: { result, isDownloaded, image in
+                                imageView.image = image
+                            })
+                        } else {
+                            leadingAnchor = imageView.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 40.0)
+                            let image = UIImage(named: "Conversation---Purple", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
                             imageView.image = image
                             imageView.image = image
-                        })
+                        }
                     }
                     }
                 }
                 }
+                leadingAnchor.isActive = true
+                
                 let titleView = UILabel()
                 let titleView = UILabel()
                 content.addSubview(titleView)
                 content.addSubview(titleView)
                 titleView.translatesAutoresizingMaskIntoConstraints = false
                 titleView.translatesAutoresizingMaskIntoConstraints = false
                 NSLayoutConstraint.activate([
                 NSLayoutConstraint.activate([
                     titleView.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 10.0),
                     titleView.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 10.0),
-                    titleView.topAnchor.constraint(equalTo: content.topAnchor, constant: 10.0),
                     titleView.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -40.0),
                     titleView.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -40.0),
                 ])
                 ])
-                titleView.text = data.name
                 titleView.font = UIFont.systemFont(ofSize: 14, weight: .medium)
                 titleView.font = UIFont.systemFont(ofSize: 14, weight: .medium)
                 
                 
-                let messageView = UILabel()
-                content.addSubview(messageView)
-                messageView.translatesAutoresizingMaskIntoConstraints = false
-                NSLayoutConstraint.activate([
-                    messageView.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 10.0),
-                    messageView.topAnchor.constraint(equalTo: titleView.bottomAnchor, constant: 2.0),
-                    messageView.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -40.0),
-                ])
-                messageView.textColor = .gray
-                if data.messageText.contains("■") {
-                    data.messageText = data.messageText.components(separatedBy: "■")[0]
-                    data.messageText = data.messageText.trimmingCharacters(in: .whitespacesAndNewlines)
-                }
-                let text = Utils.previewMessageText(chat: data)
-                let idMe = User.getMyPin() as String?
-                if let attributeText = text as? NSMutableAttributedString {
-                    let stringMessage = NSMutableAttributedString(string: "")
-                    if data.fpin == idMe {
-                        if data.lock == "1" {
-                            if data.messageScope == "4" {
-                                stringMessage.append(NSAttributedString(string: "You".localized() + ": ", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12, weight: .medium)]))
-                            }
-                            stringMessage.append(("🚫 _"+"You were deleted this message".localized()+"_").richText())
-                        } else {
-                            let imageStatus = NSTextAttachment()
-                            let status = getRealStatus(messageId: data.messageId)
-                            if status == "0" {
-                                imageStatus.image = UIImage(systemName: "xmark.circle")!.withTintColor(UIColor.red, renderingMode: .alwaysOriginal)
-                            } else if status == "1" {
-                                imageStatus.image = UIImage(systemName: "clock.arrow.circlepath")!.withTintColor(UIColor.lightGray, renderingMode: .alwaysOriginal)
-                            } else if status == "2" {
-                                imageStatus.image = UIImage(named: "checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
-                            } else if (status == "3") {
-                                imageStatus.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
-                            } else if (status == "8") {
-                                imageStatus.image = UIImage(named: "message_status_ack", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
-                            } else {
-                                imageStatus.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.systemBlue)
-                            }
-                            imageStatus.bounds = CGRect(x: 0, y: -2, width: 15, height: 15)
-                            let imageStatusString = NSAttributedString(attachment: imageStatus)
-                            stringMessage.append(imageStatusString)
-                            stringMessage.append(NSAttributedString(string: " "))
-                            if data.messageScope == "4" {
-                                stringMessage.append(NSAttributedString(string: "You".localized() + ": ", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12, weight: .medium)]))
-                            }
-                            stringMessage.append(attributeText)
-                        }
-                    } else {
-                        if data.messageScope == "4" {
-                            stringMessage.append(NSAttributedString(string: User.getData(pin: data.fpin, lPin: data.pin)!.firstName + ": ", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12, weight: .medium)]))
-                        }
-                        if data.lock == "1" {
-                            stringMessage.append(("🚫 _"+"This message was deleted".localized()+"_").richText())
-                        } else {
-                            stringMessage.append(attributeText)
-                        }
-                    }
-                    messageView.attributedText = stringMessage
-                }
-                messageView.numberOfLines = 2
-                
                 let timeView = UILabel()
                 let timeView = UILabel()
-                content.addSubview(timeView)
-                timeView.translatesAutoresizingMaskIntoConstraints = false
-                NSLayoutConstraint.activate([
-                    timeView.topAnchor.constraint(equalTo: content.topAnchor, constant: 10.0),
-                    timeView.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -20.0),
-                ])
-                timeView.textColor = .gray
-                timeView.font = UIFont.systemFont(ofSize: 14)
-                
-                let date = Date(milliseconds: Int64(data.serverDate)!)
-                let calendar = Calendar.current
-                
-                if (calendar.isDateInToday(date)) {
-                    let formatter = DateFormatter()
-                    formatter.dateFormat = "HH:mm"
-                    formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
-                    timeView.text = formatter.string(from: date as Date)
-                } else {
-                    let startOfNow = calendar.startOfDay(for: Date())
-                    let startOfTimeStamp = calendar.startOfDay(for: date)
-                    let components = calendar.dateComponents([.day], from: startOfNow, to: startOfTimeStamp)
-                    let day = -(components.day!)
-                    if day == 1 {
-                        timeView.text = "Yesterday".localized()
-                    } else {
-                        if day < 7 {
-                            let formatter = DateFormatter()
-                            formatter.dateFormat = "EEEE"
-                            let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
-                            if lang == "id" {
-                                formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
-                            }
-                            timeView.text = formatter.string(from: date)
-                        } else {
-                            let formatter = DateFormatter()
-                            formatter.dateFormat = "M/dd/yy"
-                            formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
-                            let stringFormat = formatter.string(from: date as Date)
-                            timeView.text = stringFormat
-                        }
-                    }
-                }
+                let viewCounter = UIView()
                 
                 
                 if data.counter != "0" {
                 if data.counter != "0" {
                     timeView.textColor = .systemRed
                     timeView.textColor = .systemRed
-                    let viewCounter = UIView()
                     content.addSubview(viewCounter)
                     content.addSubview(viewCounter)
                     viewCounter.translatesAutoresizingMaskIntoConstraints = false
                     viewCounter.translatesAutoresizingMaskIntoConstraints = false
                     NSLayoutConstraint.activate([
                     NSLayoutConstraint.activate([
-                        viewCounter.topAnchor.constraint(equalTo: timeView.bottomAnchor, constant: 5.0),
-                        viewCounter.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -20),
                         viewCounter.widthAnchor.constraint(greaterThanOrEqualToConstant: 20),
                         viewCounter.widthAnchor.constraint(greaterThanOrEqualToConstant: 20),
                         viewCounter.heightAnchor.constraint(equalToConstant: 20)
                         viewCounter.heightAnchor.constraint(equalToConstant: 20)
                     ])
                     ])
@@ -1327,8 +1308,147 @@ extension ContactChatViewController {
                     labelCounter.textColor = .secondaryColor
                     labelCounter.textColor = .secondaryColor
                     labelCounter.textAlignment = .center
                     labelCounter.textAlignment = .center
                 }
                 }
+                
+                if !data.isParent {
+                    titleView.topAnchor.constraint(equalTo: content.topAnchor, constant: 10.0).isActive = true
+                    titleView.text = data.name
+                    
+                    content.addSubview(timeView)
+                    timeView.translatesAutoresizingMaskIntoConstraints = false
+                    NSLayoutConstraint.activate([
+                        timeView.topAnchor.constraint(equalTo: content.topAnchor, constant: 10.0),
+                        timeView.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -20.0),
+                    ])
+                    timeView.textColor = .gray
+                    timeView.font = UIFont.systemFont(ofSize: 14)
+                    
+                    let date = Date(milliseconds: Int64(data.serverDate) ?? 0)
+                    let calendar = Calendar.current
+                    
+                    if (calendar.isDateInToday(date)) {
+                        let formatter = DateFormatter()
+                        formatter.dateFormat = "HH:mm"
+                        formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
+                        timeView.text = formatter.string(from: date as Date)
+                    } else {
+                        let startOfNow = calendar.startOfDay(for: Date())
+                        let startOfTimeStamp = calendar.startOfDay(for: date)
+                        let components = calendar.dateComponents([.day], from: startOfNow, to: startOfTimeStamp)
+                        let day = -(components.day!)
+                        if day == 1 {
+                            timeView.text = "Yesterday".localized()
+                        } else {
+                            if day < 7 {
+                                let formatter = DateFormatter()
+                                formatter.dateFormat = "EEEE"
+                                let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
+                                if lang == "id" {
+                                    formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
+                                }
+                                timeView.text = formatter.string(from: date)
+                            } else {
+                                let formatter = DateFormatter()
+                                formatter.dateFormat = "M/dd/yy"
+                                formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
+                                let stringFormat = formatter.string(from: date as Date)
+                                timeView.text = stringFormat
+                            }
+                        }
+                    }
+                    
+                    let messageView = UILabel()
+                    content.addSubview(messageView)
+                    messageView.translatesAutoresizingMaskIntoConstraints = false
+                    NSLayoutConstraint.activate([
+                        messageView.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 10.0),
+                        messageView.topAnchor.constraint(equalTo: titleView.bottomAnchor),
+                        messageView.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -40.0),
+                    ])
+                    messageView.textColor = .gray
+                    if data.messageText.contains("■") {
+                        data.messageText = data.messageText.components(separatedBy: "■")[0]
+                        data.messageText = data.messageText.trimmingCharacters(in: .whitespacesAndNewlines)
+                    }
+                    let text = Utils.previewMessageText(chat: data)
+                    let idMe = User.getMyPin() as String?
+                    if let attributeText = text as? NSMutableAttributedString {
+                        let stringMessage = NSMutableAttributedString(string: "")
+                        if data.fpin == idMe {
+                            if data.lock == "1" {
+                                if data.messageScope == "4" {
+                                    stringMessage.append(NSAttributedString(string: "You".localized() + ": ", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12, weight: .medium)]))
+                                }
+                                stringMessage.append(("🚫 _"+"You were deleted this message".localized()+"_").richText())
+                            } else {
+                                let imageStatus = NSTextAttachment()
+                                let status = getRealStatus(messageId: data.messageId)
+                                if status == "0" {
+                                    imageStatus.image = UIImage(systemName: "xmark.circle")!.withTintColor(UIColor.red, renderingMode: .alwaysOriginal)
+                                } else if status == "1" {
+                                    imageStatus.image = UIImage(systemName: "clock.arrow.circlepath")!.withTintColor(UIColor.lightGray, renderingMode: .alwaysOriginal)
+                                } else if status == "2" {
+                                    imageStatus.image = UIImage(named: "checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
+                                } else if (status == "3") {
+                                    imageStatus.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
+                                } else if (status == "8") {
+                                    imageStatus.image = UIImage(named: "message_status_ack", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
+                                } else {
+                                    imageStatus.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.systemBlue)
+                                }
+                                imageStatus.bounds = CGRect(x: 0, y: -5, width: 15, height: 15)
+                                let imageStatusString = NSAttributedString(attachment: imageStatus)
+                                stringMessage.append(imageStatusString)
+                                stringMessage.append(NSAttributedString(string: " "))
+                                if data.messageScope == "4" {
+                                    stringMessage.append(NSAttributedString(string: "You".localized() + ": ", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12, weight: .medium)]))
+                                }
+                                stringMessage.append(attributeText)
+                            }
+                        } else {
+                            if data.messageScope == "4" {
+                                var fullname = User.getData(pin: data.fpin, lPin: data.pin)!.fullName
+                                let components = fullname.split(separator: " ")
+                                if components.count >= 2 {
+                                    fullname = components.prefix(2).joined(separator: " ")
+                                }
+                                stringMessage.append(NSAttributedString(string: fullname + ": ", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12, weight: .medium)]))
+                            }
+                            if data.lock == "1" {
+                                stringMessage.append(("🚫 _"+"This message was deleted".localized()+"_").richText())
+                            } else {
+                                stringMessage.append(attributeText)
+                            }
+                        }
+                        messageView.attributedText = stringMessage
+                    }
+                    messageView.numberOfLines = 2
+                    
+                    if data.counter != "0" {
+                        viewCounter.topAnchor.constraint(equalTo: timeView.bottomAnchor, constant: 5.0).isActive = true
+                        viewCounter.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -20).isActive = true
+                    }
+                } else {
+                    titleView.centerYAnchor.constraint(equalTo: content.centerYAnchor).isActive = true
+                    titleView.text = data.groupName
+                    
+                    let iconName = (data.isSelected) ? "chevron.up.circle" : "chevron.down.circle"
+                    let imageView = UIImageView(image: UIImage(systemName: iconName))
+                    imageView.tintColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
+                    content.addSubview(imageView)
+                    imageView.translatesAutoresizingMaskIntoConstraints = false
+                    NSLayoutConstraint.activate([
+                        imageView.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -20),
+                        imageView.centerYAnchor.constraint(equalTo: content.centerYAnchor),
+                        imageView.widthAnchor.constraint(equalToConstant: 20),
+                        imageView.heightAnchor.constraint(equalToConstant: 20)
+                    ])
+                    
+                    if data.counter != "0" {
+                        viewCounter.trailingAnchor.constraint(equalTo: imageView.leadingAnchor, constant: -5).isActive = true
+                        viewCounter.centerYAnchor.constraint(equalTo: content.centerYAnchor).isActive = true
+                    }
+                }
             }
             }
-            
         case 1:
         case 1:
             if segment.numberOfSegments < 3 {
             if segment.numberOfSegments < 3 {
                 cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifierGroup", for: indexPath)
                 cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifierGroup", for: indexPath)

+ 1 - 1
NexilisLite/Podfile

@@ -7,7 +7,7 @@ target 'NexilisLite' do
 
 
   # Pods for NexilisLite
   # Pods for NexilisLite
 
 
-  pod 'nuSDKService', '~> 4.0.3'
+  pod 'nuSDKService', '~> 4.0.4'
   pod 'FMDB', '~> 2.7.12'
   pod 'FMDB', '~> 2.7.12'
   pod 'NotificationBannerSwift', '3.1.0'
   pod 'NotificationBannerSwift', '3.1.0'
   pod 'Alamofire', '~> 5.10.1'
   pod 'Alamofire', '~> 5.10.1'