Преглед на файлове

update add uclist hierarchy and add secure browser

alqindiirsyam преди 6 месеца
родител
ревизия
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 */; };
 		2401CEA3275490E600B323BB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2401CEA2275490E600B323BB /* Assets.xcassets */; };
 		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 */; };
 		A42ED92227F30BA200B0FAB7 /* FirstTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A42ED92127F30BA200B0FAB7 /* FirstTabViewController.swift */; };
 		A42ED92427F3FC2F00B0FAB7 /* SecondTabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A42ED92327F3FC2F00B0FAB7 /* SecondTabViewController.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 */; };
 		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 */; };
@@ -31,14 +30,15 @@
 		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 */; };
 		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 */
 
 /* Begin PBXContainerItemProxy section */
-		CD865A882D5B17B2007F741E /* PBXContainerItemProxy */ = {
+		CD08A1662D5EEDA5005B4EAC /* PBXContainerItemProxy */ = {
 			isa = PBXContainerItemProxy;
 			containerPortal = 2401CE8E275490DB00B323BB /* Project object */;
 			proxyType = 1;
-			remoteGlobalIDString = CD865A7F2D5B17B2007F741E;
+			remoteGlobalIDString = CD08A15D2D5EEDA5005B4EAC;
 			remoteInfo = AppBuilderShare;
 		};
 /* End PBXContainerItemProxy section */
@@ -54,23 +54,13 @@
 			name = "Embed Frameworks";
 			runOnlyForDeploymentPostprocessing = 0;
 		};
-		CD865AA62D5B2325007F741E /* Embed Frameworks */ = {
-			isa = PBXCopyFilesBuildPhase;
-			buildActionMask = 2147483647;
-			dstPath = "";
-			dstSubfolderSpec = 10;
-			files = (
-			);
-			name = "Embed Frameworks";
-			runOnlyForDeploymentPostprocessing = 0;
-		};
 		CDEE3DD129B06E1E00B420E5 /* Embed Foundation Extensions */ = {
 			isa = PBXCopyFilesBuildPhase;
 			buildActionMask = 2147483647;
 			dstPath = "";
 			dstSubfolderSpec = 13;
 			files = (
-				CD865A8A2D5B17B2007F741E /* AppBuilderShare.appex in Embed Foundation Extensions */,
+				CD08A1682D5EEDA5005B4EAC /* AppBuilderShare.appex in Embed Foundation Extensions */,
 			);
 			name = "Embed Foundation Extensions";
 			runOnlyForDeploymentPostprocessing = 0;
@@ -78,7 +68,6 @@
 /* End PBXCopyFilesBuildPhase 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>"; };
 		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>"; };
@@ -88,12 +77,14 @@
 		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>"; };
 		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>"; };
 		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>"; };
 		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>"; };
 		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>"; };
@@ -106,21 +97,20 @@
 		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>"; };
 		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 */
 
 /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
-		CD865A8B2D5B17B2007F741E /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
+		CD08A1692D5EEDA5005B4EAC /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
 			isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
 			membershipExceptions = (
 				Info.plist,
 			);
-			target = CD865A7F2D5B17B2007F741E /* AppBuilderShare */;
+			target = CD08A15D2D5EEDA5005B4EAC /* AppBuilderShare */;
 		};
 /* End PBXFileSystemSynchronizedBuildFileExceptionSet 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 */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -128,11 +118,11 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				58945074FC459F87D8C4EDA4 /* Pods_AppBuilder.framework in Frameworks */,
+				D6B1C354D062B60F7CAABAED /* Pods_AppBuilder.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
-		CD865A7D2D5B17B2007F741E /* Frameworks */ = {
+		CD08A15B2D5EEDA5005B4EAC /* Frameworks */ = {
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
@@ -146,8 +136,8 @@
 			isa = PBXGroup;
 			children = (
 				2401CE98275490DB00B323BB /* AppBuilder */,
-				CD865A812D5B17B2007F741E /* AppBuilderShare */,
-				76BBCA5783FCBB787B29FC82 /* Frameworks */,
+				CD08A15F2D5EEDA5005B4EAC /* AppBuilderShare */,
+				D5D9E949851220D48FDB3E78 /* Frameworks */,
 				6E32BCCF4DE50EE1A90E8AAE /* Pods */,
 				2401CE97275490DB00B323BB /* Products */,
 			);
@@ -157,7 +147,7 @@
 			isa = PBXGroup;
 			children = (
 				2401CE96275490DB00B323BB /* AppBuilder.app */,
-				CD865A802D5B17B2007F741E /* AppBuilderShare.appex */,
+				CD08A15E2D5EEDA5005B4EAC /* AppBuilderShare.appex */,
 			);
 			name = Products;
 			sourceTree = "<group>";
@@ -196,16 +186,16 @@
 		6E32BCCF4DE50EE1A90E8AAE /* Pods */ = {
 			isa = PBXGroup;
 			children = (
-				005671E57652801092CB2F74 /* Pods-AppBuilder.debug.xcconfig */,
-				77BA1C01F16978F06C6E2031 /* Pods-AppBuilder.release.xcconfig */,
+				4CBBF6A59F76BC3D5DA5B92B /* Pods-AppBuilder.debug.xcconfig */,
+				65A3907E764F550D0F7DD37D /* Pods-AppBuilder.release.xcconfig */,
 			);
 			path = Pods;
 			sourceTree = "<group>";
 		};
-		76BBCA5783FCBB787B29FC82 /* Frameworks */ = {
+		D5D9E949851220D48FDB3E78 /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
-				D246950EF11B497A8C2CE4B7 /* Pods_AppBuilder.framework */,
+				9E273714C9F2C0AFEADA842F /* Pods_AppBuilder.framework */,
 			);
 			name = Frameworks;
 			sourceTree = "<group>";
@@ -217,45 +207,44 @@
 			isa = PBXNativeTarget;
 			buildConfigurationList = 2401CEC0275490E600B323BB /* Build configuration list for PBXNativeTarget "AppBuilder" */;
 			buildPhases = (
-				7B7ED6AEAA0D03CB68EFC23A /* [CP] Check Pods Manifest.lock */,
+				BF5B1FC69F69170CAEF49C91 /* [CP] Check Pods Manifest.lock */,
 				2401CE92275490DB00B323BB /* Sources */,
 				2401CE93275490DB00B323BB /* Frameworks */,
 				2401CE94275490DB00B323BB /* Resources */,
 				247E0A722796969200430E5F /* Embed Frameworks */,
 				CDEE3DD129B06E1E00B420E5 /* Embed Foundation Extensions */,
-				6237F8E2B28CDC232617A789 /* [CP] Embed Pods Frameworks */,
+				07F1B34C1BB97DF342E2A084 /* [CP] Embed Pods Frameworks */,
 			);
 			buildRules = (
 			);
 			dependencies = (
-				CD865A892D5B17B2007F741E /* PBXTargetDependency */,
+				CD08A1672D5EEDA5005B4EAC /* PBXTargetDependency */,
 			);
 			name = AppBuilder;
 			productName = TestQmeraLite;
 			productReference = 2401CE96275490DB00B323BB /* AppBuilder.app */;
 			productType = "com.apple.product-type.application";
 		};
-		CD865A7F2D5B17B2007F741E /* AppBuilderShare */ = {
+		CD08A15D2D5EEDA5005B4EAC /* AppBuilderShare */ = {
 			isa = PBXNativeTarget;
-			buildConfigurationList = CD865A8C2D5B17B2007F741E /* Build configuration list for PBXNativeTarget "AppBuilderShare" */;
+			buildConfigurationList = CD08A16A2D5EEDA5005B4EAC /* Build configuration list for PBXNativeTarget "AppBuilderShare" */;
 			buildPhases = (
-				CD865A7C2D5B17B2007F741E /* Sources */,
-				CD865A7D2D5B17B2007F741E /* Frameworks */,
-				CD865A7E2D5B17B2007F741E /* Resources */,
-				CD865AA62D5B2325007F741E /* Embed Frameworks */,
+				CD08A15A2D5EEDA5005B4EAC /* Sources */,
+				CD08A15B2D5EEDA5005B4EAC /* Frameworks */,
+				CD08A15C2D5EEDA5005B4EAC /* Resources */,
 			);
 			buildRules = (
 			);
 			dependencies = (
 			);
 			fileSystemSynchronizedGroups = (
-				CD865A812D5B17B2007F741E /* AppBuilderShare */,
+				CD08A15F2D5EEDA5005B4EAC /* AppBuilderShare */,
 			);
 			name = AppBuilderShare;
 			packageProductDependencies = (
 			);
 			productName = AppBuilderShare;
-			productReference = CD865A802D5B17B2007F741E /* AppBuilderShare.appex */;
+			productReference = CD08A15E2D5EEDA5005B4EAC /* AppBuilderShare.appex */;
 			productType = "com.apple.product-type.app-extension";
 		};
 /* End PBXNativeTarget section */
@@ -270,7 +259,7 @@
 					2401CE95275490DB00B323BB = {
 						CreatedOnToolsVersion = 12.5.1;
 					};
-					CD865A7F2D5B17B2007F741E = {
+					CD08A15D2D5EEDA5005B4EAC = {
 						CreatedOnToolsVersion = 16.2;
 					};
 				};
@@ -289,7 +278,7 @@
 			projectRoot = "";
 			targets = (
 				2401CE95275490DB00B323BB /* AppBuilder */,
-				CD865A7F2D5B17B2007F741E /* AppBuilderShare */,
+				CD08A15D2D5EEDA5005B4EAC /* AppBuilderShare */,
 			);
 		};
 /* End PBXProject section */
@@ -316,7 +305,7 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
-		CD865A7E2D5B17B2007F741E /* Resources */ = {
+		CD08A15C2D5EEDA5005B4EAC /* Resources */ = {
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
@@ -326,7 +315,7 @@
 /* End PBXResourcesBuildPhase section */
 
 /* Begin PBXShellScriptBuildPhase section */
-		6237F8E2B28CDC232617A789 /* [CP] Embed Pods Frameworks */ = {
+		07F1B34C1BB97DF342E2A084 /* [CP] Embed Pods Frameworks */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
@@ -343,7 +332,7 @@
 			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AppBuilder/Pods-AppBuilder-frameworks.sh\"\n";
 			showEnvVarsInLog = 0;
 		};
-		7B7ED6AEAA0D03CB68EFC23A /* [CP] Check Pods Manifest.lock */ = {
+		BF5B1FC69F69170CAEF49C91 /* [CP] Check Pods Manifest.lock */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
@@ -383,7 +372,7 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
-		CD865A7C2D5B17B2007F741E /* Sources */ = {
+		CD08A15A2D5EEDA5005B4EAC /* Sources */ = {
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
@@ -393,10 +382,10 @@
 /* End PBXSourcesBuildPhase section */
 
 /* Begin PBXTargetDependency section */
-		CD865A892D5B17B2007F741E /* PBXTargetDependency */ = {
+		CD08A1672D5EEDA5005B4EAC /* PBXTargetDependency */ = {
 			isa = PBXTargetDependency;
-			target = CD865A7F2D5B17B2007F741E /* AppBuilderShare */;
-			targetProxy = CD865A882D5B17B2007F741E /* PBXContainerItemProxy */;
+			target = CD08A15D2D5EEDA5005B4EAC /* AppBuilderShare */;
+			targetProxy = CD08A1662D5EEDA5005B4EAC /* PBXContainerItemProxy */;
 		};
 /* End PBXTargetDependency section */
 
@@ -538,7 +527,7 @@
 		};
 		2401CEC1275490E600B323BB /* Debug */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 005671E57652801092CB2F74 /* Pods-AppBuilder.debug.xcconfig */;
+			baseConfigurationReference = 4CBBF6A59F76BC3D5DA5B92B /* Pods-AppBuilder.debug.xcconfig */;
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
@@ -574,7 +563,7 @@
 		};
 		2401CEC2275490E600B323BB /* Release */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 77BA1C01F16978F06C6E2031 /* Pods-AppBuilder.release.xcconfig */;
+			baseConfigurationReference = 65A3907E764F550D0F7DD37D /* Pods-AppBuilder.release.xcconfig */;
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
@@ -608,7 +597,7 @@
 			};
 			name = Release;
 		};
-		CD865A8D2D5B17B2007F741E /* Debug */ = {
+		CD08A16B2D5EEDA5005B4EAC /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
@@ -646,7 +635,7 @@
 			};
 			name = Debug;
 		};
-		CD865A8E2D5B17B2007F741E /* Release */ = {
+		CD08A16C2D5EEDA5005B4EAC /* Release */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
@@ -704,11 +693,11 @@
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Release;
 		};
-		CD865A8C2D5B17B2007F741E /* Build configuration list for PBXNativeTarget "AppBuilderShare" */ = {
+		CD08A16A2D5EEDA5005B4EAC /* Build configuration list for PBXNativeTarget "AppBuilderShare" */ = {
 			isa = XCConfigurationList;
 			buildConfigurations = (
-				CD865A8D2D5B17B2007F741E /* Debug */,
-				CD865A8E2D5B17B2007F741E /* Release */,
+				CD08A16B2D5EEDA5005B4EAC /* Debug */,
+				CD08A16C2D5EEDA5005B4EAC /* Release */,
 			);
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Release;

+ 256 - 166
AppBuilder/AppBuilder/SecondTabViewController.swift

@@ -18,6 +18,7 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
     var isAdmin: Bool = false
     
     var chats: [Chat] = []
+    var chatGroupMaps: [String: [Chat]] = [:]
     
     var groups: [Group] = []
     
@@ -34,6 +35,8 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
     var alertController = LibAlertController()
     
     var noData = false
+    var loadingData = true
+    var waitingLoading = false
     
     let textViewSearch = UITextField()
     let buttonImageVoiceSb = UIButton(type: .custom)
@@ -113,7 +116,12 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
                 fillteredData = self.groups.filter { $0.name.lowercased().contains(searchText.lowercased()) }
             default:
                 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 {
                     switch(selectedTag) {
                     case UNREAD_TAG :
@@ -359,20 +367,7 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
     }
     
     @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(){
@@ -622,23 +617,9 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
         }
         if segment.selectedSegmentIndex == 0 {
             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) {
         if textField == textViewSearch {
             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) {
         let data:[AnyHashable : Any] = notification.userInfo!
         if data["member"] as? String == User.getMyPin()! {
-            DispatchQueue.main.async {
-                self.getData()
-            }
+            reloadAllData()
         } else if data["state"] as? Int == 99 {
-            DispatchQueue.main.async {
-                self.getData()
-            }
+            reloadAllData()
         }
     }
     
     @objc func onReceiveMessage(notification: NSNotification) {
-        DispatchQueue.main.async {
-            self.getData()
-        }
+        reloadAllData()
     }
     
     @objc func onStatusChat(notification: NSNotification) {
@@ -890,6 +877,7 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
                 }
                 DispatchQueue.main.async {
                     self.tableView.reloadData()
+                    self.loadingData = false
                 }
             }
         }
@@ -897,7 +885,41 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
     
     func getChats(completion: @escaping ()->()) {
         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()
         }
     }
@@ -1066,6 +1088,10 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
             } else {
                 data = chats[indexPath.row]
             }
+            if data.isParent {
+                expandCollapseChats(tableView: tableView, indexPath: indexPath)
+                return
+            }
             if let chooser = isChooser {
                 if data.pin == "-999"{
                     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) {
         let group: Group
         if isFiltering {
@@ -1217,17 +1277,12 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
             } else {
                 getGroups(id: group.id) { g in
                     DispatchQueue.main.async {
-                        //print("index path section: \(indexPath.section)")
-                        //print("index path row: \(indexPath.row)")
-                        //print("index path item: \(indexPath.item)")
                         if self.isFiltering {
-//                                self.fillteredData.remove(at: indexPath.section)
                             if self.fillteredData[indexPath.section] is Group {
                                 self.groupMap[(self.fillteredData[indexPath.section] as! Group).id] = 1
                                 self.fillteredData.insert(contentsOf: g, at: indexPath.section + 1)
                             }
                         } else {
-//                                self.groups.remove(at: indexPath.section)
                             self.groupMap[self.groups[indexPath.section].id] = 1
                             self.groups.insert(contentsOf: g, at: indexPath.section + 1)
                         }
@@ -1375,7 +1430,7 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
             }
             if noData || (isFiltering && fillteredData.count == 0) {
                 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.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
                 content.addSubview(labelNochat)
@@ -1390,7 +1445,7 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
             } else {
                 if chats.count == 0 {
                     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.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
                     content.addSubview(labelNochat)
@@ -1693,12 +1748,12 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
             content.addSubview(imageView)
             imageView.translatesAutoresizingMaskIntoConstraints = false
             NSLayoutConstraint.activate([
-                imageView.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 10.0),
                 imageView.topAnchor.constraint(equalTo: content.topAnchor, constant: 10.0),
                 imageView.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -10.0),
                 imageView.widthAnchor.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.messageScope == "3" {
                     imageView.image = UIImage(named: "Profile---Purple", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
@@ -1748,141 +1803,36 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
                         }
                     }
                 } 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
-                    })
+                    }
                 }
             }
+            leadingAnchor.isActive = true
+            
             let titleView = UILabel()
             content.addSubview(titleView)
             titleView.translatesAutoresizingMaskIntoConstraints = false
             NSLayoutConstraint.activate([
                 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.text = data.name
             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()
-            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" {
                 timeView.textColor = .systemRed
-                let viewCounter = UIView()
                 content.addSubview(viewCounter)
                 viewCounter.translatesAutoresizingMaskIntoConstraints = false
                 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.heightAnchor.constraint(equalToConstant: 20)
                 ])
@@ -1909,6 +1859,146 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
                 labelCounter.textColor = .secondaryColor
                 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:
             cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifierGroup", for: indexPath)
             var content = cell.defaultContentConfiguration()

+ 2 - 2
NexilisLite/NexilisLite.podspec

@@ -8,7 +8,7 @@
 
 Pod::Spec.new do |spec|
   spec.name         = "NexilisLite"
-  spec.version      = "3.3.1"
+  spec.version      = "5.0.5"
   spec.summary      = "NexilisLite Framework"
   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...
@@ -24,7 +24,7 @@ Pod::Spec.new do |spec|
   spec.resource_bundles = { 'NexilisLite' => ['NexilisLite/Resource/**/*']}
   spec.swift_version = '5.5.1'
   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 'Alamofire', '~> 5.10.1'
   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() {
         let isChangeProfile = Utils.getSetProfile()
         if !isChangeProfile {
@@ -1228,14 +1243,23 @@ public class APIS: NSObject {
 //        } catch {
 //        }
 //        exit(0)
+        UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum)
     }
     
     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()
         UIApplication.shared.applicationIconBadgeNumber = 0
         UNUserNotificationCenter.current().removeAllDeliveredNotifications()

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

@@ -1299,13 +1299,19 @@ class IncomingThread {
         } else {
             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
                     let identifier = message.getBody(key : CoreMessage_TMessageKey.MESSAGE_ID, default_value : "")
                     let matchingNotifications = notifications.filter { $0.request.identifier == identifier }
                     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 pin: String
     public let messageId: String
-    public let counter: String
+    public var counter: String
     public var messageText: String
     public let serverDate: String
     public let image: String
@@ -29,6 +29,10 @@ public class Chat: Model {
     public let thumb: String
     public let audio: 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) {
         self.fpin = ""
@@ -51,9 +55,40 @@ public class Chat: Model {
         self.thumb = ""
         self.audio = ""
         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.pin = pin
         self.messageId = messageId
@@ -74,12 +109,20 @@ public class Chat: Model {
         self.thumb = thumb
         self.audio = audio
         self.gif = gif
+        self.groupId = groupId
+        self.groupName = groupName
+        self.isSelected = isSelected
+        self.isParent = isParent
     }
     
     public static func == (lhs: Chat, rhs: Chat) -> Bool {
         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 {
         return ""
     }
@@ -135,15 +178,15 @@ public class Chat: Model {
                     lastQuery = "m.audio_id IS NOT NULL AND m.audio_id != ''"
                 }
                 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
-                            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
-                            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
-                            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
-                            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
                             """
                 if !lastQuery.isEmpty {
@@ -182,7 +225,9 @@ public class Chat: Model {
                                         lock: !lastQuery.isEmpty ? cursorData.string(forColumnIndex: 12) ?? "" : cursorData.string(forColumnIndex: 16) ?? "",
                                         thumb: !lastQuery.isEmpty ? cursorData.string(forColumnIndex: 3) ?? "" : "",
                                         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)
                     }
                     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_SOCIAL_COMMERCE = 101
     public static let IDX_NEWS = 102
+    public static let IDX_SECURE_BROWSER = 105
     
     public static var callAPNActivated = false
     
@@ -164,6 +165,8 @@ public class Nexilis: NSObject {
         imageCache.countLimit = 100
         imageCache.totalCostLimit = 1024 * 1024 * 200
         
+        UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum)
+        
         DispatchQueue.global().async {
             do {
                 if Utils.getFinishInitPrefsr() {
@@ -181,7 +184,7 @@ public class Nexilis: NSObject {
                     id = String(sDID[sDID.index(sDID.endIndex, offsetBy: -5)...])
                     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) {
                     Thread.sleep(forTimeInterval: 0.5)
                 }
@@ -297,41 +300,46 @@ public class Nexilis: NSObject {
         }
         
         _ = 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 {
-            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?.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?.prepareToPlay()
-            
-//            print("END INITIATE ringbacktone")
-            
+            ringbacktonePlayer?.play()
         } catch {
             
         }
     }
     
+    public static func stopRingbacktoneCall() {
+        ringbacktonePlayer?.stop()
+    }
+    
     public static func addFB(viewController: UIViewController, fromMAB: Bool) {
         if let keyWindow = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) {
             keyWindow.addSubview(floatingButton)
@@ -629,23 +637,23 @@ public class Nexilis: NSObject {
         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 {
         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 {
             HTTPCookieStorage.shared.setCookies(convertJSONStringToCookies(jsonString: Utils.getCookiesMobileForStorage()), for: url, mainDocumentURL: nil)
         }
-        print("[App] getAddress:", result)
+//        print("[App] getAddress:", result)
         return result
     }
     
@@ -1097,6 +1105,8 @@ public class Nexilis: NSObject {
             APIS.openFavoriteMessage()
         } else if index == IDX_SECURE_FOLDER {
             APIS.openSecureFolder()
+        } else if index == IDX_SECURE_BROWSER {
+            APIS.openSecureBrowser()
         } else {
             openApp(id: id)
         }
@@ -3819,89 +3829,122 @@ extension Nexilis: MessageDelegate {
                     subtitle.textColor = .white
                     
                     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 {
-                                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 {
-                            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() {
                         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()
     let closeButton = UIButton()
     public var customUrl = ""
+    public var isSecureBrowser = false
+    let textField = UITextField()
     
     var isAllowSpeech = false
     
@@ -44,9 +46,46 @@ public class BNIBookingWebView: UIViewController, WKNavigationDelegate, UIScroll
         configuration.applicationNameForUserAgent = finalUserAgent
         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)
         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.bottomAnchor.constraint(equalTo: view.bottomAnchor).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")"
         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) {

+ 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.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
         if (dataMessages[indexPath.row]["lock"] != nil && (dataMessages[indexPath.row]["lock"])! as? String == "1") {
             if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
@@ -4575,14 +4575,14 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
         }
         
         if !audioChat.isEmpty {
-            textChat = textChat!.components(separatedBy: "|")[0]
+            textChat = textChat.components(separatedBy: "|")[0]
         }
         
         let imageSticker = UIImageView()
         
         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
                         let title = json["title"]  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.bottomAnchor.constraint(equalTo: messageText.topAnchor, constant: -5).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 {
-                    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.contentMode = .scaleAspectFit
             }
             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()
             }
         } 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()
         }
         
         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 {
-                    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
                 let urlPattern = "(https?://|www\\.)\\S+"
                 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 {
-                        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(.foregroundColor, value: UIColor.blue, range: nsRange)
                             finalAtribute.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: nsRange)
@@ -4672,8 +4672,8 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
         }
         
         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
             }
         }
@@ -5008,12 +5008,12 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
             let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
             let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
             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]
             if let dirPath = paths.first {
                 let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(fileChat)
                 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)
                         if (sizeOfFile < 1) {
                             sizeOfFile = Int(dataFile.count / 1000)
@@ -5033,7 +5033,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
                 }
                 else if FileEncryption.shared.isSecureExists(filename: fileChat) {
                     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)
                             if (sizeOfFile < 1) {
                                 sizeOfFile = Int(dataFile.count / 1000)
@@ -5088,7 +5088,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
             nameFile.widthAnchor.constraint(lessThanOrEqualToConstant: 200).isActive = true
             nameFile.font = UIFont.systemFont(ofSize: 12, weight: .medium)
             nameFile.textColor = .white
-            nameFile.text = originalMessageText?.components(separatedBy: "|")[0]
+            nameFile.text = originalMessageText.components(separatedBy: "|")[0]
             
             if (dataMessages[indexPath.row]["progress"] as! Double != 100.0) {
                 let containerLoading = UIView()
@@ -5139,9 +5139,9 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
         
         let containerLinkMessage = UIView()
         var isLoadingShowLink = false
-        if thumbChat.isEmpty && fileChat.isEmpty && !textChat!.isEmpty {
+        if thumbChat.isEmpty && fileChat.isEmpty && !textChat.isEmpty {
             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://") })
             if indexFirstLinkSplitBreak != nil {
                 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 chatGroupMaps: [String: [Chat]] = [:]
+    
     var contacts: [User] = []
     
     var groups: [Group] = []
@@ -45,6 +47,9 @@ class ContactChatViewController: UITableViewController {
     
     var noData = false
     
+    var loadingData = true
+    var waitingLoading = false
+    
     var noUCList = false
     
     func filterContentForSearchText(_ searchText: String) {
@@ -64,7 +69,12 @@ class ContactChatViewController: UITableViewController {
                 case 2:
                     fillteredData = self.groups.filter { $0.name.lowercased().contains(searchText.lowercased()) }
                 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 {
                 switch segment.selectedSegmentIndex {
@@ -254,30 +264,35 @@ class ContactChatViewController: UITableViewController {
 //        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) {
-        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) {
-        DispatchQueue.main.async {
-            self.getData()
-        }
+        reloadAllData()
     }
     
     @objc func onStatusChat(notification: NSNotification) {
@@ -411,7 +426,41 @@ class ContactChatViewController: UITableViewController {
     
     func getChats(completion: @escaping ()->()) {
         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()
         }
     }
@@ -654,6 +703,10 @@ extension ContactChatViewController {
                 } else {
                     data = chats[indexPath.row]
                 }
+                if data.isParent {
+                    expandCollapseChats(tableView: tableView, indexPath: indexPath)
+                    return
+                }
                 if let chooser = isChooser {
                     var exblock = User.getDataCanNil(pin: data.pin)?.ex_block
                     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) {
         let group: Group
         if isFilltering {
@@ -831,21 +918,15 @@ extension ContactChatViewController {
             } else {
                 getGroups(id: group.id) { g in
                     DispatchQueue.main.async {
-                        //print("index path section: \(indexPath.section)")
-                        //print("index path row: \(indexPath.row)")
-                        //print("index path item: \(indexPath.item)")
                         if self.isFilltering {
-//                                self.fillteredData.remove(at: indexPath.section)
                             if self.fillteredData[indexPath.section] is Group {
                                 self.groupMap[(self.fillteredData[indexPath.section] as! Group).id] = 1
                                 self.fillteredData.insert(contentsOf: g, at: indexPath.section + 1)
                             }
                         } else {
-//                                self.groups.remove(at: indexPath.section)
                             self.groupMap[self.groups[indexPath.section].id] = 1
                             self.groups.insert(contentsOf: g, at: indexPath.section + 1)
                         }
-                        //print("groupMap: \(self.groupMap)")
                         tableView.reloadData()
                         
                         self.expandCollapseGroup(tableView: tableView, indexPath: IndexPath(row: 0, section: indexPath.section + 1))
@@ -1116,12 +1197,12 @@ extension ContactChatViewController {
                 content.addSubview(imageView)
                 imageView.translatesAutoresizingMaskIntoConstraints = false
                 NSLayoutConstraint.activate([
-                    imageView.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 10.0),
                     imageView.topAnchor.constraint(equalTo: content.topAnchor, constant: 10.0),
                     imageView.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -10.0),
                     imageView.widthAnchor.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.messageScope == "3" {
                         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" {
                     imageView.frame = CGRect(x: 0, y: 0, width: 55.0, height: 55.0)
                     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
                             if error == nil {
                                 imageView.animationImages = image?.images
@@ -1171,136 +1252,36 @@ extension ContactChatViewController {
                             }
                         }
                     } 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
-                        })
+                        }
                     }
                 }
+                leadingAnchor.isActive = true
+                
                 let titleView = UILabel()
                 content.addSubview(titleView)
                 titleView.translatesAutoresizingMaskIntoConstraints = false
                 NSLayoutConstraint.activate([
                     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.text = data.name
                 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()
-                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" {
                     timeView.textColor = .systemRed
-                    let viewCounter = UIView()
                     content.addSubview(viewCounter)
                     viewCounter.translatesAutoresizingMaskIntoConstraints = false
                     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.heightAnchor.constraint(equalToConstant: 20)
                     ])
@@ -1327,8 +1308,147 @@ extension ContactChatViewController {
                     labelCounter.textColor = .secondaryColor
                     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:
             if segment.numberOfSegments < 3 {
                 cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifierGroup", for: indexPath)

+ 1 - 1
NexilisLite/Podfile

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