Browse Source

update fix bugs and tuning

alqindiirsyam 10 tháng trước cách đây
mục cha
commit
2ebb182189
28 tập tin đã thay đổi với 518 bổ sung230 xóa
  1. BIN
      .DS_Store
  2. BIN
      AppBuilder/.DS_Store
  3. 29 29
      AppBuilder/AppBuilder.xcodeproj/project.pbxproj
  4. 11 10
      AppBuilder/AppBuilder/Info.plist
  5. 44 5
      AppBuilder/AppBuilder/SecondTabViewController.swift
  6. BIN
      NexilisLite/.DS_Store
  7. BIN
      NexilisLite/NexilisLite/.DS_Store
  8. BIN
      NexilisLite/NexilisLite/Resource/.DS_Store
  9. 0 21
      NexilisLite/NexilisLite/Resource/Assets.xcassets/pb_gpt_bot.imageset/Contents.json
  10. BIN
      NexilisLite/NexilisLite/Resource/Assets.xcassets/pb_gpt_bot.imageset/pb_gpt_bot.png
  11. BIN
      NexilisLite/NexilisLite/Resource/Gifs/pb_gpt_bot.gif
  12. 34 13
      NexilisLite/NexilisLite/Resource/Palio.storyboard
  13. 2 1
      NexilisLite/NexilisLite/Resource/id.lproj/Localizable.strings
  14. 14 2
      NexilisLite/NexilisLite/Source/FloatingButton/FloatingButton.swift
  15. 2 0
      NexilisLite/NexilisLite/Source/Model/Chat.swift
  16. 2 2
      NexilisLite/NexilisLite/Source/Utils.swift
  17. 16 1
      NexilisLite/NexilisLite/Source/View/Call/QmeraAudioViewController.swift
  18. 32 5
      NexilisLite/NexilisLite/Source/View/Call/QmeraVideoViewController.swift
  19. 14 23
      NexilisLite/NexilisLite/Source/View/Chat/ChatGPTBotView.swift
  20. 23 14
      NexilisLite/NexilisLite/Source/View/Chat/EditorGroup.swift
  21. 49 24
      NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift
  22. 71 18
      NexilisLite/NexilisLite/Source/View/Chat/MessageInfo.swift
  23. 2 2
      NexilisLite/NexilisLite/Source/View/Chat/PreviewAttachmentImageVideo.swift
  24. 127 42
      NexilisLite/NexilisLite/Source/View/Control/ContactChatViewController.swift
  25. 22 3
      NexilisLite/NexilisLite/Source/View/Control/GroupCreateViewController.swift
  26. 16 7
      NexilisLite/NexilisLite/Source/View/Control/HistoryBroadcastViewController.swift
  27. 1 1
      NexilisLite/NexilisLite/Source/View/Streaming/QmeraCreateStreamingViewController.swift
  28. 7 7
      NexilisLite/NexilisLite/Source/View/Streaming/QmeraStreamingViewController.swift

BIN
.DS_Store


BIN
AppBuilder/.DS_Store


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

@@ -14,6 +14,7 @@
 		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 */; };
+		97CE94031DAB5279520168B7 /* Pods_AppBuilder.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AC609786ACEC0341CC921B30 /* 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 */; };
@@ -31,7 +32,6 @@
 		CD9D59E32BEE1D30008014B4 /* nu_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = CD9D59D82BEE1D30008014B4 /* nu_icon.png */; };
 		CDEE3DCC29B06E1E00B420E5 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDEE3DCB29B06E1E00B420E5 /* NotificationService.swift */; };
 		CDEE3DD029B06E1E00B420E5 /* NotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = CDEE3DC929B06E1E00B420E5 /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
-		EAFBE0E8AD3B23894589DF04 /* Pods_AppBuilder.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAFD2830E24FC73F8535FA02 /* Pods_AppBuilder.framework */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -78,13 +78,13 @@
 		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>"; };
-		8FF65CE34BA5274D8AB725AC /* 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>"; };
-		96F1B564A5FE6B73402D6BE8 /* 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>"; };
+		4541B48357BC75B7A8C1B96A /* 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>"; };
 		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>"; };
-		AAFD2830E24FC73F8535FA02 /* Pods_AppBuilder.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AppBuilder.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+		AC609786ACEC0341CC921B30 /* Pods_AppBuilder.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AppBuilder.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+		BF569AE8FC2850F8F59938BC /* 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>"; };
 		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,7 +106,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				EAFBE0E8AD3B23894589DF04 /* Pods_AppBuilder.framework in Frameworks */,
+				97CE94031DAB5279520168B7 /* Pods_AppBuilder.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -127,7 +127,7 @@
 				CDEE3DCA29B06E1E00B420E5 /* NotificationService */,
 				2401CE97275490DB00B323BB /* Products */,
 				6E32BCCF4DE50EE1A90E8AAE /* Pods */,
-				3A008D0E0464C8833443BB64 /* Frameworks */,
+				4055E37109B5A1180A3BAA3C /* Frameworks */,
 			);
 			sourceTree = "<group>";
 		};
@@ -170,10 +170,10 @@
 			path = AppBuilder;
 			sourceTree = "<group>";
 		};
-		3A008D0E0464C8833443BB64 /* Frameworks */ = {
+		4055E37109B5A1180A3BAA3C /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
-				AAFD2830E24FC73F8535FA02 /* Pods_AppBuilder.framework */,
+				AC609786ACEC0341CC921B30 /* Pods_AppBuilder.framework */,
 			);
 			name = Frameworks;
 			sourceTree = "<group>";
@@ -181,8 +181,8 @@
 		6E32BCCF4DE50EE1A90E8AAE /* Pods */ = {
 			isa = PBXGroup;
 			children = (
-				96F1B564A5FE6B73402D6BE8 /* Pods-AppBuilder.debug.xcconfig */,
-				8FF65CE34BA5274D8AB725AC /* Pods-AppBuilder.release.xcconfig */,
+				BF569AE8FC2850F8F59938BC /* Pods-AppBuilder.debug.xcconfig */,
+				4541B48357BC75B7A8C1B96A /* Pods-AppBuilder.release.xcconfig */,
 			);
 			path = Pods;
 			sourceTree = "<group>";
@@ -203,13 +203,13 @@
 			isa = PBXNativeTarget;
 			buildConfigurationList = 2401CEC0275490E600B323BB /* Build configuration list for PBXNativeTarget "AppBuilder" */;
 			buildPhases = (
-				FB27CF2C874E911216C61B3A /* [CP] Check Pods Manifest.lock */,
+				55F7B6EB91289342CF0B558C /* [CP] Check Pods Manifest.lock */,
 				2401CE92275490DB00B323BB /* Sources */,
 				2401CE93275490DB00B323BB /* Frameworks */,
 				2401CE94275490DB00B323BB /* Resources */,
 				247E0A722796969200430E5F /* Embed Frameworks */,
 				CDEE3DD129B06E1E00B420E5 /* Embed Foundation Extensions */,
-				EB45D03BBFCFC690746CD427 /* [CP] Embed Pods Frameworks */,
+				A786313812243B4782A0082F /* [CP] Embed Pods Frameworks */,
 			);
 			buildRules = (
 			);
@@ -306,43 +306,43 @@
 /* End PBXResourcesBuildPhase section */
 
 /* Begin PBXShellScriptBuildPhase section */
-		EB45D03BBFCFC690746CD427 /* [CP] Embed Pods Frameworks */ = {
+		55F7B6EB91289342CF0B558C /* [CP] Check Pods Manifest.lock */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
 			);
 			inputFileListPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-AppBuilder/Pods-AppBuilder-frameworks-${CONFIGURATION}-input-files.xcfilelist",
 			);
-			name = "[CP] Embed Pods Frameworks";
+			inputPaths = (
+				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+				"${PODS_ROOT}/Manifest.lock",
+			);
+			name = "[CP] Check Pods Manifest.lock";
 			outputFileListPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-AppBuilder/Pods-AppBuilder-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+			);
+			outputPaths = (
+				"$(DERIVED_FILE_DIR)/Pods-AppBuilder-checkManifestLockResult.txt",
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
-			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AppBuilder/Pods-AppBuilder-frameworks.sh\"\n";
+			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
 			showEnvVarsInLog = 0;
 		};
-		FB27CF2C874E911216C61B3A /* [CP] Check Pods Manifest.lock */ = {
+		A786313812243B4782A0082F /* [CP] Embed Pods Frameworks */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
 			);
 			inputFileListPaths = (
+				"${PODS_ROOT}/Target Support Files/Pods-AppBuilder/Pods-AppBuilder-frameworks-${CONFIGURATION}-input-files.xcfilelist",
 			);
-			inputPaths = (
-				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
-				"${PODS_ROOT}/Manifest.lock",
-			);
-			name = "[CP] Check Pods Manifest.lock";
+			name = "[CP] Embed Pods Frameworks";
 			outputFileListPaths = (
-			);
-			outputPaths = (
-				"$(DERIVED_FILE_DIR)/Pods-AppBuilder-checkManifestLockResult.txt",
+				"${PODS_ROOT}/Target Support Files/Pods-AppBuilder/Pods-AppBuilder-frameworks-${CONFIGURATION}-output-files.xcfilelist",
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
-			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AppBuilder/Pods-AppBuilder-frameworks.sh\"\n";
 			showEnvVarsInLog = 0;
 		};
 /* End PBXShellScriptBuildPhase section */
@@ -519,7 +519,7 @@
 		};
 		2401CEC1275490E600B323BB /* Debug */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 96F1B564A5FE6B73402D6BE8 /* Pods-AppBuilder.debug.xcconfig */;
+			baseConfigurationReference = BF569AE8FC2850F8F59938BC /* Pods-AppBuilder.debug.xcconfig */;
 			buildSettings = {
 				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
@@ -551,7 +551,7 @@
 		};
 		2401CEC2275490E600B323BB /* Release */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 8FF65CE34BA5274D8AB725AC /* Pods-AppBuilder.release.xcconfig */;
+			baseConfigurationReference = 4541B48357BC75B7A8C1B96A /* Pods-AppBuilder.release.xcconfig */;
 			buildSettings = {
 				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;

+ 11 - 10
AppBuilder/AppBuilder/Info.plist

@@ -114,6 +114,7 @@
 		<string>ms-outlook</string>
 		<string>readdle-spark</string>
 		<string>ymail</string>
+        <string>comgooglemaps</string>
 	</array>
 	<key>LSRequiresIPhoneOS</key>
 	<true/>
@@ -146,16 +147,16 @@
 	<array>
 		<string>INSendMessageIntent</string>
 	</array>
-	<key>UIAppFonts</key>
-	<array>
-		<string>Poppins-Light.ttf</string>
-		<string>Poppins-Medium.ttf</string>
-		<string>Poppins-SemiBoldItalic.ttf</string>
-		<string>Poppins-Regular.ttf</string>
-		<string>Poppins-LightItalic.ttf</string>
-		<string>Poppins-SemiBold.ttf</string>
-		<string>Poppins-MediumItalic.ttf</string>
-	</array>
+<!--	<key>UIAppFonts</key>-->
+<!--	<array>-->
+<!--		<string>Poppins-Light.ttf</string>-->
+<!--		<string>Poppins-Medium.ttf</string>-->
+<!--		<string>Poppins-SemiBoldItalic.ttf</string>-->
+<!--		<string>Poppins-Regular.ttf</string>-->
+<!--		<string>Poppins-LightItalic.ttf</string>-->
+<!--		<string>Poppins-SemiBold.ttf</string>-->
+<!--		<string>Poppins-MediumItalic.ttf</string>-->
+<!--	</array>-->
 	<key>UIApplicationSceneManifest</key>
 	<dict>
 		<key>UIApplicationSupportsMultipleScenes</key>

+ 44 - 5
AppBuilder/AppBuilder/SecondTabViewController.swift

@@ -1104,7 +1104,12 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
                 dismiss(animated: true, completion: nil)
                 return
             }
-            if data.messageScope == "3" {
+            if data.pin == "-997" {
+                let smartChatVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "chatGptVC") as! ChatGPTBotView
+                smartChatVC.hidesBottomBarWhenPushed = true
+                smartChatVC.fromNotification = false
+                navigationController?.show(smartChatVC, sender: nil)
+            } else if data.messageScope == "3" {
                 let editorPersonalVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorPersonalVC") as! EditorPersonal
                 editorPersonalVC.hidesBottomBarWhenPushed = true
                 editorPersonalVC.unique_l_pin = data.pin
@@ -1724,16 +1729,50 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
                 imageView.widthAnchor.constraint(equalToConstant: 55.0),
                 imageView.heightAnchor.constraint(equalToConstant: 55.0)
             ])
-            if data.profile.isEmpty && data.pin != "-999" {
+            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)
                 } else {
                     imageView.image = UIImage(named: "Conversation---Purple", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
                 }
+            } 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") {
+                    imageView.sd_setImage(with: urlGif) { (image, error, cacheType, imageURL) in
+                        if error == nil {
+                            imageView.animationImages = image?.images
+                            imageView.animationDuration = image?.duration ?? 0.0
+                            imageView.animationRepeatCount = 0
+                            imageView.startAnimating()
+                        }
+                    }
+                }
             } 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
-                    imageView.image = image
-                })
+                if !Utils.getIconDock().isEmpty {
+                    let urlString = Utils.getUrlDock()!
+                    if let cachedImage = ImageCache.shared.image(forKey: urlString) {
+                        let imageData = cachedImage
+                        imageView.image = imageData
+                    } else {
+                        DispatchQueue.global().async{
+                            Utils.fetchDataWithCookiesAndUserAgent(from: URL(string: urlString)!) { data, response, error in
+                                guard let data = data, error == nil else { return }
+                                DispatchQueue.main.async() {
+                                    if UIImage(data: data) != nil {
+                                        let imageData = UIImage(data: data)!
+                                        imageView.image = imageData
+                                        ImageCache.shared.save(image: imageData, forKey: urlString)
+                                    }
+                                }
+                            }
+                        }
+                    }
+                } 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
+                        imageView.image = image
+                    })
+                }
             }
             let titleView = UILabel()
             content.addSubview(titleView)

BIN
NexilisLite/.DS_Store


BIN
NexilisLite/NexilisLite/.DS_Store


BIN
NexilisLite/NexilisLite/Resource/.DS_Store


+ 0 - 21
NexilisLite/NexilisLite/Resource/Assets.xcassets/pb_gpt_bot.imageset/Contents.json

@@ -1,21 +0,0 @@
-{
-  "images" : [
-    {
-      "filename" : "pb_gpt_bot.png",
-      "idiom" : "universal",
-      "scale" : "1x"
-    },
-    {
-      "idiom" : "universal",
-      "scale" : "2x"
-    },
-    {
-      "idiom" : "universal",
-      "scale" : "3x"
-    }
-  ],
-  "info" : {
-    "author" : "xcode",
-    "version" : 1
-  }
-}

BIN
NexilisLite/NexilisLite/Resource/Assets.xcassets/pb_gpt_bot.imageset/pb_gpt_bot.png


BIN
NexilisLite/NexilisLite/Resource/Gifs/pb_gpt_bot.gif


+ 34 - 13
NexilisLite/NexilisLite/Resource/Palio.storyboard

@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23094" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
     <device id="retina6_1" orientation="portrait" appearance="light"/>
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23084"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
         <capability name="Image references" minToolsVersion="12.0"/>
         <capability name="Safe area layout guides" minToolsVersion="9.0"/>
         <capability name="System colors in document resources" minToolsVersion="11.0"/>
@@ -1214,7 +1214,27 @@
                                             <rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/>
                                             <autoresizingMask key="autoresizingMask"/>
                                             <subviews>
-                                                <textField opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Name" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="agZ-7H-Lfa">
+                                                <textField opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Title Group" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="agZ-7H-Lfa">
+                                                    <rect key="frame" x="20" y="0.0" width="374" height="44"/>
+                                                    <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                                                    <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                                                    <textInputTraits key="textInputTraits" autocapitalizationType="words"/>
+                                                </textField>
+                                            </subviews>
+                                        </tableViewCellContentView>
+                                    </tableViewCell>
+                                </cells>
+                            </tableViewSection>
+                            <tableViewSection id="FF5-hQ-v1U">
+                                <cells>
+                                    <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="6fV-Ax-hVZ">
+                                        <rect key="frame" x="0.0" y="97.5" width="414" height="43.5"/>
+                                        <autoresizingMask key="autoresizingMask"/>
+                                        <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="6fV-Ax-hVZ" id="ohg-8B-qhq">
+                                            <rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/>
+                                            <autoresizingMask key="autoresizingMask"/>
+                                            <subviews>
+                                                <textField opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Sub Group" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="CE0-fn-PUw">
                                                     <rect key="frame" x="20" y="0.0" width="374" height="44"/>
                                                     <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                                                     <fontDescription key="fontDescription" type="system" pointSize="14"/>
@@ -1234,6 +1254,7 @@
                     <navigationItem key="navigationItem" id="Lrr-2J-ttT"/>
                     <connections>
                         <outlet property="name" destination="agZ-7H-Lfa" id="94q-DG-ecZ"/>
+                        <outlet property="subGroup" destination="CE0-fn-PUw" id="Aqd-1x-DUC"/>
                     </connections>
                 </tableViewController>
                 <placeholder placeholderIdentifier="IBFirstResponder" id="mhk-p6-iGB" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
@@ -3337,37 +3358,37 @@
             <color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
         </systemColor>
         <systemColor name="secondarySystemBackgroundColor">
-            <color red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+            <color red="0.94901960784313721" green="0.94901960784313721" blue="0.96862745098039216" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
         </systemColor>
         <systemColor name="systemBackgroundColor">
             <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
         </systemColor>
         <systemColor name="systemBlueColor">
-            <color red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+            <color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
         </systemColor>
         <systemColor name="systemGray2Color">
-            <color red="0.68235294120000001" green="0.68235294120000001" blue="0.69803921570000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+            <color red="0.68235294117647061" green="0.68235294117647061" blue="0.69803921568627447" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
         </systemColor>
         <systemColor name="systemGray3Color">
-            <color red="0.78039215689999997" green="0.78039215689999997" blue="0.80000000000000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+            <color red="0.7803921568627451" green="0.7803921568627451" blue="0.80000000000000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
         </systemColor>
         <systemColor name="systemGray4Color">
-            <color red="0.81960784310000001" green="0.81960784310000001" blue="0.83921568629999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+            <color red="0.81960784313725488" green="0.81960784313725488" blue="0.83921568627450982" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
         </systemColor>
         <systemColor name="systemGrayColor">
-            <color red="0.5568627451" green="0.5568627451" blue="0.57647058819999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+            <color red="0.55686274509803924" green="0.55686274509803924" blue="0.57647058823529407" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
         </systemColor>
         <systemColor name="systemGreenColor">
-            <color red="0.20392156859999999" green="0.78039215689999997" blue="0.34901960780000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+            <color red="0.20392156862745098" green="0.7803921568627451" blue="0.34901960784313724" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
         </systemColor>
         <systemColor name="systemIndigoColor">
-            <color red="0.34509803919999998" green="0.33725490200000002" blue="0.83921568629999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+            <color red="0.34509803921568627" green="0.33725490196078434" blue="0.83921568627450982" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
         </systemColor>
         <systemColor name="systemRedColor">
-            <color red="1" green="0.23137254900000001" blue="0.18823529410000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+            <color red="1" green="0.23137254901960785" blue="0.18823529411764706" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
         </systemColor>
         <systemColor name="tintColor">
-            <color red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+            <color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
         </systemColor>
     </resources>
 </document>

+ 2 - 1
NexilisLite/NexilisLite/Resource/id.lproj/Localizable.strings

@@ -383,4 +383,5 @@
 "Translating..." = "Menerjemahkan...";
 "Getting chat suggestion..." = "Mendapatkan saran obrolan...";
 "There is an error occurred while translating your message. Please try again or check your network connection." = "Terjadi kesalahan saat menerjemahkan pesan Anda. Silakan coba lagi atau periksa koneksi jaringan Anda.";
-
+"Sub Group" = "Sub Grup";
+"Optional" = "Opsional";

+ 14 - 2
NexilisLite/NexilisLite/Source/FloatingButton/FloatingButton.swift

@@ -10,7 +10,7 @@ import nuSDKService
 import NotificationBannerSwift
 
 
-public class FloatingButton: UIView {
+public class FloatingButton: UIView, UIGestureRecognizerDelegate {
     
     var groupView: UIStackView!
     var scrollView: UIScrollView!
@@ -146,9 +146,10 @@ public class FloatingButton: UIView {
         let qmeraTap = UITapGestureRecognizer(target: self, action: #selector(qmeraTap))
         qmeraTap.numberOfTouchesRequired = 1
         nexilis_button.addGestureRecognizer(qmeraTap)
+        qmeraTap.delegate = self
         
         let qmeraLongPress = UILongPressGestureRecognizer(target: self, action: #selector(qmeraLongPress(gestureRecognizer:)))
-        nexilis_button.addGestureRecognizer(qmeraLongPress)
+        self.addGestureRecognizer(qmeraLongPress)
         
         addSubview(nexilis_button)
         
@@ -243,6 +244,11 @@ public class FloatingButton: UIView {
         UIApplication.shared.windows.first?.rootViewController?.view.addGestureRecognizer(tapGesture)
     }
     
+    @objc func handleTap(_ sender: UITapGestureRecognizer) {
+        let location = sender.location(in: self)
+        print("Tap location: \(location)")
+    }
+    
     public func setImageWithURL(_ isDocked: Bool) {
         if configModeFB != MODE_VERTICAL_FLOATING_BUTTON {
             return
@@ -374,6 +380,8 @@ public class FloatingButton: UIView {
                         newButton.restorationIdentifier = package_id
                         newButton.accessibilityIdentifier = app_id
                         newButton.addTarget(self, action: #selector(fbTap), for: .touchUpInside)
+                        let qmeraLongPress = UILongPressGestureRecognizer(target: self, action: #selector(qmeraLongPress(gestureRecognizer:)))
+                        newButton.addGestureRecognizer(qmeraLongPress)
                     }
                 }
             } else {
@@ -477,6 +485,8 @@ public class FloatingButton: UIView {
                                 newButton.restorationIdentifier = package_id
                                 newButton.accessibilityIdentifier = app_id
                                 newButton.addTarget(self, action: #selector(fbTap), for: .touchUpInside)
+                                let qmeraLongPress = UILongPressGestureRecognizer(target: self, action: #selector(qmeraLongPress(gestureRecognizer:)))
+                                newButton.addGestureRecognizer(qmeraLongPress)
                             }
                         }
                     }
@@ -540,6 +550,8 @@ public class FloatingButton: UIView {
             newButton.restorationIdentifier = "default_fb\(data[i])"
             newButton.accessibilityIdentifier = ""
             newButton.addTarget(self, action: #selector(fbTap), for: .touchUpInside)
+            let qmeraLongPress = UILongPressGestureRecognizer(target: self, action: #selector(qmeraLongPress(gestureRecognizer:)))
+            newButton.addGestureRecognizer(qmeraLongPress)
         }
     }
     

+ 2 - 0
NexilisLite/NexilisLite/Source/Model/Chat.swift

@@ -139,6 +139,8 @@ public class Chat: Model {
                             union
                             select m.f_pin, \(!lastQuery.isEmpty ? "m.l_pin, m.message_id" : "ms.l_pin, ms.message_id"), \(!lastQuery.isEmpty ? "m.thumb_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 \(!lastQuery.isEmpty ? lastQuery : "ms.message_id = m.message_id \(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'")")
                             union
+                            select m.f_pin, \(!lastQuery.isEmpty ? "m.l_pin, m.message_id" : "ms.l_pin, ms.message_id"), \(!lastQuery.isEmpty ? "m.thumb_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 \(!lastQuery.isEmpty ? lastQuery : "ms.message_id = m.message_id \(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'")")
+                            union
                             select m.f_pin, \(!lastQuery.isEmpty ? "m.l_pin, m.message_id" : "ms.l_pin, ms.message_id"), \(!lastQuery.isEmpty ? "m.thumb_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 \(!lastQuery.isEmpty ? lastQuery : "ms.message_id = m.message_id \(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'")")
                             union
                             select m.f_pin, \(!lastQuery.isEmpty ? "m.l_pin, m.message_id" : "ms.l_pin, ms.message_id"), \(!lastQuery.isEmpty ? "m.thumb_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 \(!lastQuery.isEmpty ? lastQuery : "ms.message_id = m.message_id \(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'")")

+ 2 - 2
NexilisLite/NexilisLite/Source/Utils.swift

@@ -449,14 +449,14 @@ public final class Utils {
         return "https://nexilis.io/"
     }
     
-    static func getIconDock() -> String {
+    public static func getIconDock() -> String {
         if let value: String = SecureUserDefaults.shared.value(forKey: "app_builder_icon_dock") {
             return value
         }
         return ""
     }
     
-    static func getUrlDock() -> String? {
+    public static func getUrlDock() -> String? {
         return Utils.getURLBase() + "get_file_from_path?img=" + Utils.getIconDock()
     }
     

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

@@ -254,11 +254,13 @@ class QmeraAudioViewController: UIViewController {
     override func viewWillDisappear(_ animated: Bool) {
         UIDevice.current.isProximityMonitoringEnabled = false
         NotificationCenter.default.removeObserver(self)
+        Nexilis.floatingButton.isHidden = false
     }
     
     deinit {
         UIDevice.current.isProximityMonitoringEnabled = false
         NotificationCenter.default.removeObserver(self)
+        Nexilis.floatingButton.isHidden = false
     }
     
     override func viewDidAppear(_ animated: Bool) {
@@ -268,6 +270,8 @@ class QmeraAudioViewController: UIViewController {
     override func viewDidLoad() {
         super.viewDidLoad()
         
+        Nexilis.floatingButton.isHidden = true
+        
         let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .systemUltraThinMaterialDark))
         effectView.frame = view.frame
         view.insertSubview(effectView, at: 0)
@@ -605,7 +609,7 @@ class QmeraAudioViewController: UIViewController {
     @objc func didMute(sender: Any?) {
         isMuted = !isMuted
         mic.isSelected = isMuted
-        API.mmc(int: 0, boolean: isMuted)
+        API.mmc(int: 1, boolean: isMuted)
     }
     
     @objc func didInvite(sender: Any?) {
@@ -897,6 +901,9 @@ class QmeraAudioViewController: UIViewController {
                             SecureUserDefaults.shared.set("\(members)", forKey: "membersCC")
                         }
                     }
+                    if buttonWB.isEnabled {
+                        buttonWB.isEnabled = false
+                    }
                 }
             } else if state == Nexilis.AUDIO_CALL_END || (!ticketId.isEmpty && state == Nexilis.VIDEO_CALL_END) {
                 if isOutgoing {
@@ -975,6 +982,8 @@ class QmeraAudioViewController: UIViewController {
                             self.didEnd(sender: true)
                         }
                         return
+                    } else if users.count == 1 && !buttonWB.isEnabled {
+                        buttonWB.isEnabled = true
                     }
                 }
 //                if users.count == 0 {
@@ -1004,6 +1013,9 @@ class QmeraAudioViewController: UIViewController {
                             SecureUserDefaults.shared.set("\(members)", forKey: "membersCC")
                         }
                     }
+                    if users.count == 1 && !buttonWB.isEnabled {
+                        buttonWB.isEnabled = true
+                    }
                 }
                 if users.count == 0 {
                     DispatchQueue.main.async {
@@ -1036,6 +1048,9 @@ class QmeraAudioViewController: UIViewController {
                             SecureUserDefaults.shared.set("\(members)", forKey: "membersCC")
                         }
                     }
+                    if users.count == 1 && !buttonWB.isEnabled {
+                        buttonWB.isEnabled = true
+                    }
                 }
                 if users.count == 0 {
                     DispatchQueue.main.async {

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

@@ -137,6 +137,7 @@ class QmeraVideoViewController: UIViewController {
         navigationController?.navigationBar.topItem?.backBarButtonItem = nil
         navigationController?.interactivePopGestureRecognizer?.isEnabled = true
         NotificationCenter.default.removeObserver(self)
+        Nexilis.floatingButton.isHidden = false
     }
     
     override func viewWillDisappear(_ animated: Bool) {
@@ -148,11 +149,13 @@ class QmeraVideoViewController: UIViewController {
             navigationController?.interactivePopGestureRecognizer?.isEnabled = true
             NotificationCenter.default.removeObserver(self)
         }
+        Nexilis.floatingButton.isHidden = false
     }
 
     override func viewDidLoad() {
         super.viewDidLoad()
         Nexilis.setWhiteboardReceiver(receiver: self)
+        Nexilis.floatingButton.isHidden = true
         self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.black]
         navigationController?.changeAppearance(clear: true)
         
@@ -1001,7 +1004,7 @@ class QmeraVideoViewController: UIViewController {
     
     @objc func muted(sender: Any?) {
         isMuted = !isMuted
-        API.mmc(int: 0, boolean: isMuted)
+        API.mmc(int: 1, boolean: isMuted)
         DispatchQueue.main.async {
             if (self.isMuted) {
                 self.buttonMuted.backgroundColor = .lightGray
@@ -1307,6 +1310,12 @@ class QmeraVideoViewController: UIViewController {
                         if (self.dataPerson.count == 2) {
                             self.containerLabelName.forEach({ $0.subviews.forEach({ $0.removeFromSuperview() }) })
                             self.scrollRemoteView.subviews.forEach({ $0.removeFromSuperview() })
+                            if !self.buttonWB.isEnabled {
+                                self.buttonWB.isEnabled = true
+                            }
+                            if !self.buttonRotate.isEnabled {
+                                self.buttonRotate.isEnabled = true
+                            }
                         } else {
                             self.containerLabelName[indexPerson! + indexPerson!].subviews.forEach({ $0.removeFromSuperview() })
                             self.scrollRemoteView.subviews[indexPerson! + indexPerson!].removeFromSuperview()
@@ -1335,10 +1344,10 @@ class QmeraVideoViewController: UIViewController {
                         }
                     }
                     
-                    if self.dataPerson.count == 1 {
-                        self.transformZoomAfterNewUserMore2 = false
-                        self.zoomView.transform   = CGAffineTransform.init(scaleX: 1.9, y: 2.2).rotated(by: (CGFloat.pi)/2)
-                    }
+//                    if self.dataPerson.count == 1 {
+//                        self.transformZoomAfterNewUserMore2 = false
+//                        self.zoomView.transform   = CGAffineTransform.init(scaleX: 1.9, y: 2.2).rotated(by: (CGFloat.pi)/2)
+//                    }
                 }
             }
         } else if (state == Nexilis.OFFLINE) {
@@ -1365,6 +1374,12 @@ class QmeraVideoViewController: UIViewController {
                         if (self.dataPerson.count == 2) {
                             self.containerLabelName.forEach({ $0.subviews.forEach({ $0.removeFromSuperview() }) })
                             self.scrollRemoteView.subviews.forEach({ $0.removeFromSuperview() })
+                            if !self.buttonWB.isEnabled {
+                                self.buttonWB.isEnabled = true
+                            }
+                            if !self.buttonRotate.isEnabled {
+                                self.buttonRotate.isEnabled = true
+                            }
                         } else {
                             self.containerLabelName[indexPerson! + indexPerson!].subviews.forEach({ $0.removeFromSuperview() })
                             self.scrollRemoteView.subviews[indexPerson! + indexPerson!].removeFromSuperview()
@@ -1436,6 +1451,12 @@ class QmeraVideoViewController: UIViewController {
                         if (self.dataPerson.count == 2) {
                             self.containerLabelName.forEach({ $0.subviews.forEach({ $0.removeFromSuperview() }) })
                             self.scrollRemoteView.subviews.forEach({ $0.removeFromSuperview() })
+                            if !self.buttonWB.isEnabled {
+                                self.buttonWB.isEnabled = true
+                            }
+                            if !self.buttonRotate.isEnabled {
+                                self.buttonRotate.isEnabled = true
+                            }
                         } else {
                             self.containerLabelName[indexPerson! + indexPerson!].subviews.forEach({ $0.removeFromSuperview() })
                             self.scrollRemoteView.subviews[indexPerson! + indexPerson!].removeFromSuperview()
@@ -1542,6 +1563,12 @@ class QmeraVideoViewController: UIViewController {
                         labelName.textAlignment = .center
                         labelName.textColor = .white
                     }
+                    if self.buttonWB.isEnabled {
+                        self.buttonWB.isEnabled = false
+                    }
+                    if self.buttonRotate.isEnabled {
+                        self.buttonRotate.isEnabled = false
+                    }
                 }
             }
         }

+ 14 - 23
NexilisLite/NexilisLite/Source/View/Chat/ChatGPTBotView.swift

@@ -10,7 +10,7 @@ import Alamofire
 import NotificationBannerSwift
 import nuSDKService
 
-class ChatGPTBotView: UIViewController, UIGestureRecognizerDelegate {
+public class ChatGPTBotView: UIViewController, UIGestureRecognizerDelegate {
     public var dataPerson : [String: String?] = [
         "f_pin" : "-997",
         "firstName" : "GPT SmartBot",
@@ -207,36 +207,19 @@ class ChatGPTBotView: UIViewController, UIGestureRecognizerDelegate {
             }
         })
         let pin = "-997"
-        var counter : Int? = nil
-        Database.shared.database?.inTransaction({ (fmdb, rollback) in
-            do {
-                if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select counter from MESSAGE_SUMMARY where l_pin = '\(pin)'"), cursor.next() {
-                    counter = Int(cursor.int(forColumnIndex: 0))
-                    counter! += 0
-                    cursor.close()
-                    //print("select db message summary")
-                }
-            } catch {
-                rollback.pointee = true
-                print("Access database error: \(error.localizedDescription)")
-            }
-        })
-        if counter == nil {
-            counter = 0
-            //print("set counter message summary")
-        }
         Database.shared.database?.inTransaction({ (fmdb, rollback) in
             do {
                 _ = try Database.shared.insertRecord(fmdb: fmdb, table: "MESSAGE_SUMMARY", cvalues: [
                     "l_pin" : pin,
                     "message_id" : message_id,
-                    "counter" : counter!
+                    "counter" : 0
                 ], replace: true)
             } catch {
                 rollback.pointee = true
                 print("Access database error: \(error.localizedDescription)")
             }
         })
+        NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
         var row: [String: Any?] = [:]
         row["message_id"] = message_id
         row["f_pin"] = "-997"
@@ -626,11 +609,19 @@ class ChatGPTBotView: UIViewController, UIGestureRecognizerDelegate {
             imageProfile.clipsToBounds = true
             var count = 0
             viewAppBar.addSubview(imageProfile)
-            imageProfile.image = UIImage(named: "pb_gpt_bot", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
-            imageProfile.contentMode = .scaleAspectFit
+            if let urlGif = Bundle.resourceBundle(for: Nexilis.self).url(forResource: "pb_gpt_bot", withExtension: "gif") {
+                imageProfile.sd_setImage(with: urlGif) { (image, error, cacheType, imageURL) in
+                    if error == nil {
+                        imageProfile.animationImages = image?.images
+                        imageProfile.animationDuration = image?.duration ?? 0.0
+                        imageProfile.animationRepeatCount = 0
+                        imageProfile.startAnimating()
+                    }
+                }
+            }
             let titleNavigation = UILabel(frame: CGRect(x: 35, y: 0, width: viewAppBar.frame.size.width - 250, height: 44))
             viewAppBar.addSubview(titleNavigation)
-            titleNavigation.set(image: UIImage(named: "ic_official_flag", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: "  GPT SmartBot", size: 15, y: -4)
+            titleNavigation.text = "GPT SmartBot"
             titleNavigation.textColor = .white
             titleNavigation.font = UIFont.systemFont(ofSize: 12).bold
             

+ 23 - 14
NexilisLite/NexilisLite/Source/View/Chat/EditorGroup.swift

@@ -887,7 +887,7 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
                 }
                 if let cell = self.tableChatView.cellForRow(at: indexPath) {
                     for view in cell.contentView.subviews {
-                        if !(view is UILabel) && !(view is UIImageView) {
+                        if !(view is UITextView) && !(view is UIImageView) {
                             for viewInContainer in view.subviews {
                                 if viewInContainer is UIImageView {
                                     if viewInContainer.subviews.count == 0 {
@@ -928,19 +928,24 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
                     }
                     if let cell = self.tableChatView.cellForRow(at: indexPath) {
                         for view in cell.contentView.subviews {
-                            if !(view is UILabel) && !(view is UIImageView) {
+                            if !(view is UITextView) && !(view is UIImageView) {
                                 for viewSubviews in view.subviews {
-                                    if !(viewSubviews is UILabel) {
+                                    if !(viewSubviews is UITextView) {
                                         for viewInContainer in viewSubviews.subviews {
-                                            if !(viewInContainer is UILabel) && !(viewInContainer is UIImageView) {
-                                                if viewInContainer.layer.sublayers!.count < 2 {
-                                                    return
+                                            if !(viewInContainer is UITextView) && !(viewInContainer is UIImageView) {
+                                                if let cont = viewInContainer.layer.sublayers {
+                                                    if cont.count < 2 {
+                                                        return
+                                                    }
                                                 }
-                                                let loading = viewInContainer.layer.sublayers![1] as! CAShapeLayer
-                                                loading.strokeEnd = CGFloat(progress / 100)
-                                                if (progress == 100.0) {
-                                                    self.dataMessages[idx!]["progress"] = progress
-                                                    self.tableChatView.reloadRows(at: [indexPath], with: .none)
+                                                if let layers = viewInContainer.layer.sublayers {
+                                                    if let loading = layers [1] as? CAShapeLayer {
+                                                        loading.strokeEnd = CGFloat(progress / 100)
+                                                        if (progress == 100.0) {
+                                                            self.dataMessages[idx!]["progress"] = progress
+                                                            self.tableChatView.reloadRows(at: [indexPath], with: .none)
+                                                        }
+                                                    }
                                                 }
                                             }
                                         }
@@ -1165,9 +1170,13 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
                         }
                     }
                     else if (chatData.keys.contains("message_id")) {
-                        var idx = self.dataMessages.firstIndex(where: { "'\(String(describing: $0["message_id"] as? String))'" == chatData["message_id"]! })
-                        if let idxMessageIdParent = self.groupImages.firstIndex(where: { $0.value.contains(where: { $0.messageId == chatData["message_id"]! }) }) {
-                            if let idxInImages = self.groupImages[idxMessageIdParent].value.firstIndex(where: { $0.messageId == chatData["message_id"]! }) {
+                        var idMessage = dataMessage.getBody(key: "message_id")
+                        if idMessage.contains("'") {
+                            idMessage = idMessage.replacingOccurrences(of: "'", with: "")
+                        }
+                        var idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == idMessage })
+                        if let idxMessageIdParent = self.groupImages.firstIndex(where: { $0.value.contains(where: { $0.messageId == idMessage }) }) {
+                            if let idxInImages = self.groupImages[idxMessageIdParent].value.firstIndex(where: { $0.messageId == idMessage }) {
                                 self.groupImages[idxMessageIdParent].value[idxInImages].status = chatData[CoreMessage_TMessageKey.STATUS]!
                                 self.groupImages[idxMessageIdParent].value[idxInImages].dataMessage["status"] = chatData[CoreMessage_TMessageKey.STATUS]!
                             }

+ 49 - 24
NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift

@@ -240,12 +240,14 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
         locationManager.delegate = self
         locationManager.requestWhenInUseAuthorization()
         
-        // Check if location services are enabled
-        if CLLocationManager.locationServicesEnabled() {
-            locationManager.desiredAccuracy = kCLLocationAccuracyBest
-            locationManager.startUpdatingLocation()
-        } else {
-            print("Location services are not enabled.")
+        DispatchQueue.global().async { [self] in
+            // Check if location services are enabled
+            if CLLocationManager.locationServicesEnabled() {
+                locationManager.desiredAccuracy = kCLLocationAccuracyBest
+                locationManager.startUpdatingLocation()
+            } else {
+                print("Location services are not enabled.")
+            }
         }
         
         if dataMessageForward != nil {
@@ -738,9 +740,23 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
             } else if dataPerson["f_pin"]!! == "-999" {
                 viewAppBar.addSubview(imageProfile)
                 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 {
-                        imageProfile.image = UIImage(data: dataImage!)
+                    let urlString = Utils.getUrlDock()!
+                    if let cachedImage = ImageCache.shared.image(forKey: urlString) {
+                        let imageData = cachedImage
+                        imageProfile.image = imageData
+                    } else {
+                        DispatchQueue.global().async{
+                            Utils.fetchDataWithCookiesAndUserAgent(from: URL(string: urlString)!) { data, response, error in
+                                guard let data = data, error == nil else { return }
+                                DispatchQueue.main.async() {
+                                    if UIImage(data: data) != nil {
+                                        let imageData = UIImage(data: data)!
+                                        imageProfile.image = imageData
+                                        ImageCache.shared.save(image: imageData, forKey: urlString)
+                                    }
+                                }
+                            }
+                        }
                     }
                 } else {
                     imageProfile.image = UIImage(named: "pb_button", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
@@ -1289,7 +1305,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                 }
                 if let cell = self.tableChatView.cellForRow(at: indexPath) {
                     for view in cell.contentView.subviews {
-                        if !(view is UILabel) && !(view is UIImageView) {
+                        if !(view is UITextView) && !(view is UIImageView) {
                             for viewInContainer in view.subviews {
                                 if viewInContainer is UIImageView {
                                     if viewInContainer.subviews.count == 0 {
@@ -1337,19 +1353,24 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                     }
                     if let cell = self.tableChatView.cellForRow(at: indexPath) {
                         for view in cell.contentView.subviews {
-                            if !(view is UILabel) && !(view is UIImageView) {
+                            if !(view is UITextView) && !(view is UIImageView) {
                                 for viewSubviews in view.subviews {
-                                    if !(viewSubviews is UILabel) {
+                                    if !(viewSubviews is UITextView) {
                                         for viewInContainer in viewSubviews.subviews {
-                                            if !(viewInContainer is UILabel) && !(viewInContainer is UIImageView) {
-                                                if viewInContainer.layer.sublayers!.count < 2 {
-                                                    return
+                                            if !(viewInContainer is UITextView) && !(viewInContainer is UIImageView) {
+                                                if let cont = viewInContainer.layer.sublayers {
+                                                    if cont.count < 2 {
+                                                        return
+                                                    }
                                                 }
-                                                let loading = viewInContainer.layer.sublayers![1] as! CAShapeLayer
-                                                loading.strokeEnd = CGFloat(progress / 100)
-                                                if (progress == 100.0) {
-                                                    self.dataMessages[idx!]["progress"] = progress
-                                                    self.tableChatView.reloadRows(at: [indexPath], with: .none)
+                                                if let layers = viewInContainer.layer.sublayers {
+                                                    if let loading = layers [1] as? CAShapeLayer {
+                                                        loading.strokeEnd = CGFloat(progress / 100)
+                                                        if (progress == 100.0) {
+                                                            self.dataMessages[idx!]["progress"] = progress
+                                                            self.tableChatView.reloadRows(at: [indexPath], with: .none)
+                                                        }
+                                                    }
                                                 }
                                             }
                                         }
@@ -1752,9 +1773,13 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                         }
                     }
                     else if (chatData.keys.contains("message_id")) {
-                        var idx = self.dataMessages.firstIndex(where: { "'\(String(describing: $0["message_id"] as? String))'" == chatData["message_id"]! })
-                        if let idxMessageIdParent = self.groupImages.firstIndex(where: { $0.value.contains(where: { $0.messageId == chatData["message_id"]! }) }) {
-                            if let idxInImages = self.groupImages[idxMessageIdParent].value.firstIndex(where: { $0.messageId == chatData["message_id"]! }) {
+                        var idMessage = dataMessage.getBody(key: "message_id")
+                        if idMessage.contains("'") {
+                            idMessage = idMessage.replacingOccurrences(of: "'", with: "")
+                        }
+                        var idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == idMessage })
+                        if let idxMessageIdParent = self.groupImages.firstIndex(where: { $0.value.contains(where: { $0.messageId == idMessage }) }) {
+                            if let idxInImages = self.groupImages[idxMessageIdParent].value.firstIndex(where: { $0.messageId == idMessage }) {
                                 self.groupImages[idxMessageIdParent].value[idxInImages].status = chatData[CoreMessage_TMessageKey.STATUS]!
                                 self.groupImages[idxMessageIdParent].value[idxInImages].dataMessage["status"] = chatData[CoreMessage_TMessageKey.STATUS]!
                             }
@@ -6560,7 +6585,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
             return
         }
         DispatchQueue.global().async {
-            let result = Nexilis.write(message: CoreMessage_TMessageBank.getAckLocationMessage(f_pin: dataMessages[indexPath.row]["f_pin"] as! String, message_id: dataMessages[indexPath.row]["message_id"] as! String, l_pin: dataMessages[indexPath.row]["l_pin"] as! String, server_date: "\(Date().currentTimeMillis())", message_scope_id: dataMessages[indexPath.row]["message_scope_id"] as! String, longitude: self.latitude, latitude: self.longitude, description: ""))
+            let result = Nexilis.write(message: CoreMessage_TMessageBank.getAckLocationMessage(f_pin: dataMessages[indexPath.row]["f_pin"] as! String, message_id: dataMessages[indexPath.row]["message_id"] as! String, l_pin: dataMessages[indexPath.row]["l_pin"] as! String, server_date: "\(Date().currentTimeMillis())", message_scope_id: dataMessages[indexPath.row]["message_scope_id"] as! String, longitude: self.longitude, latitude: self.latitude, description: ""))
             if result != nil {
                 Database.shared.database?.inTransaction({ (fmdb, rollback) in
                     do {

+ 71 - 18
NexilisLite/NexilisLite/Source/View/Chat/MessageInfo.swift

@@ -17,8 +17,7 @@ class MessageInfo: UIViewController, UITableViewDelegate, UITableViewDataSource,
     var dataPerson: [String: String?] = [:]
     var dataGroup: [String: Any?] = [:]
     var isPersonal = true
-    var finishGetLocation = false
-    let locationManager = CLLocationManager()
+    let geocoder = CLGeocoder()
 
     override func viewDidLoad() {
         super.viewDidLoad()
@@ -38,14 +37,10 @@ class MessageInfo: UIViewController, UITableViewDelegate, UITableViewDataSource,
         getData()
         dateMessage = chatDate(stringDate: data["server_date"] as! String)
         
-        locationManager.delegate = self
-        locationManager.requestWhenInUseAuthorization()
-        if CLLocationManager.locationServicesEnabled() {
-            locationManager.desiredAccuracy = kCLLocationAccuracyBest
-            locationManager.startUpdatingLocation()
-        }
-        
         tableStatus.reloadData()
+        DispatchQueue.global().async{
+            self.getAllLocationDesc()
+        }
         
         view.addSubview(tableStatus)
         tableStatus.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor)
@@ -91,13 +86,35 @@ class MessageInfo: UIViewController, UITableViewDelegate, UITableViewDataSource,
         for data in dataStatus {
             let latitude = CLLocationDegrees(data["latitude"] as? String ?? "")!
             let longitude = CLLocationDegrees(data["longitude"] as? String ?? "")!
+            guard (-90...90).contains(latitude), (-180...180).contains(longitude) else {
+                print("Invalid coordinates!")
+                continue
+            }
             let location = CLLocation(latitude: latitude, longitude: longitude)
-            let geocoder = CLGeocoder()
-            print("SEKUTT \(location.coordinate.latitude), \(location.coordinate.longitude)")
+            if geocoder.isGeocoding {
+                geocoder.cancelGeocode()
+            }
+            Nexilis.dispatch = DispatchGroup()
+            Nexilis.dispatch?.enter()
             geocoder.reverseGeocodeLocation(location) { (placemarks, error) in
                 var result = ""
                 if let error = error {
-                    print("Error fetching address: \(error.localizedDescription)")
+                    if let clError = error as? CLError {
+                        switch clError.code {
+                        case .network:
+                            print("Network error: Check your internet connection.")
+                        case .geocodeFoundNoResult:
+                            print("No results found for the given coordinates.")
+                        case .geocodeCanceled:
+                            print("Geocoding request was canceled.")
+                        case .geocodeFoundPartialResult:
+                            print("Partial result found.")
+                        default:
+                            print("Error: \(clError.localizedDescription)")
+                        }
+                    } else {
+                        print("Unknown error: \(error.localizedDescription)")
+                    }
                 } else if let placemark = placemarks?.first {
                     if let locality = placemark.locality {
                         result += locality + ", "
@@ -108,15 +125,18 @@ class MessageInfo: UIViewController, UITableViewDelegate, UITableViewDataSource,
                     if let country = placemark.country {
                         result += country
                     }
-                    print("MANTAB \(result)")
+                }
+                self.dataLocation.append(result)
+                if let dispatch = Nexilis.dispatch {
+                    dispatch.leave()
                 }
             }
+            Nexilis.dispatch?.wait()
+            Nexilis.dispatch = nil
+        }
+        DispatchQueue.main.async {
+            self.tableStatus.reloadData()
         }
-    }
-    
-    public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
-        self.getAllLocationDesc()
-        locationManager.stopUpdatingLocation()
     }
     
     func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
@@ -270,6 +290,9 @@ class MessageInfo: UIViewController, UITableViewDelegate, UITableViewDataSource,
                             })
                             
                             content.text = dataProfile["name"]!
+                            if dataLocation.count > 0 {
+                                content.text = dataProfile["name"]! + " at (\(dataLocation[indexPath.row]))"
+                            }
                             
                             let date = Date(milliseconds: Int64(dataStatusAck[indexPath.row]["time_ack"] as! String) ?? 100)
                             let formatter = DateFormatter()
@@ -403,6 +426,9 @@ class MessageInfo: UIViewController, UITableViewDelegate, UITableViewDataSource,
                 if !data.isEmpty && data["read_receipts"] as? String == "8"{
                     content.image = UIImage(named: "message_status_ack", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
                     content.text = "Confirmed".localized()
+                    if dataLocation.count > 0 {
+                        content.text = "Confirmed".localized() + " at (\(dataLocation[0]))"
+                    }
                     if dataStatus.count != 0 {
                         if (dataStatus[0]["time_ack"] as! String).isEmpty {
                             cell.accessoryView = noStatus
@@ -1094,6 +1120,33 @@ class MessageInfo: UIViewController, UITableViewDelegate, UITableViewDataSource,
         return cell
     }
     
+    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        if !data.isEmpty && data["read_receipts"] as? String == "8" {
+            if !isPersonal {
+                let latitude = CLLocationDegrees(dataStatus[indexPath.row]["latitude"] as? String ?? "")!
+                let longitude = CLLocationDegrees(dataStatus[indexPath.row]["longitude"] as? String ?? "")!
+                openMapApp(latitude: latitude, longitude: longitude)
+            } else {
+                let latitude = CLLocationDegrees(dataStatus[0]["latitude"] as? String ?? "")!
+                let longitude = CLLocationDegrees(dataStatus[0]["longitude"] as? String ?? "")!
+                openMapApp(latitude: latitude, longitude: longitude)
+            }
+        }
+    }
+    
+    func openMapApp(latitude: CLLocationDegrees, longitude: CLLocationDegrees) {
+        let appleMapsURL = "http://maps.apple.com/?q=\(latitude),\(longitude)"
+        let googleMapsURL = "comgooglemaps://?q=\(latitude),\(longitude)&zoom=14"
+        
+        if let googleMaps = URL(string: googleMapsURL), UIApplication.shared.canOpenURL(googleMaps) {
+            UIApplication.shared.open(googleMaps, options: [:], completionHandler: nil)
+        } else if let appleMaps = URL(string: appleMapsURL) {
+            UIApplication.shared.open(appleMaps, options: [:], completionHandler: nil)
+        } else {
+            print("No map application available.")
+        }
+    }
+    
     private func chatDate(stringDate: String) -> String {
         let date = Date(milliseconds: Int64(stringDate)!)
         let calendar = Calendar.current

+ 2 - 2
NexilisLite/NexilisLite/Source/View/Chat/PreviewAttachmentImageVideo.swift

@@ -425,9 +425,9 @@ class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITex
             }
             self.dismiss(animated: true, completion: nil)
             if (textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines) == "Send message".localized() && textFieldSend.textColor == UIColor.lightGray) {
-                delegate!.sendChatFromPreviewImage(message_text: "", attachment_flag: "2", image_id: "", video_id: renamedVideoName, thumb_id: thumbName, gif_id: renamedVideoName, viewController: self)
+                delegate!.sendChatFromPreviewImage(message_text: "", attachment_flag: "2", image_id: "", video_id: renamedVideoName, thumb_id: thumbName, gif_id: dataGIF != nil ? renamedVideoName : "", viewController: self)
             } else {
-                delegate!.sendChatFromPreviewImage(message_text: textFieldSend.text!, attachment_flag: "2", image_id: "", video_id: renamedVideoName, thumb_id: thumbName, gif_id: renamedVideoName, viewController: self)
+                delegate!.sendChatFromPreviewImage(message_text: textFieldSend.text!, attachment_flag: "2", image_id: "", video_id: renamedVideoName, thumb_id: thumbName, gif_id: dataGIF != nil ? renamedVideoName : "", viewController: self)
             }
         }
     }

+ 127 - 42
NexilisLite/NexilisLite/Source/View/Control/ContactChatViewController.swift

@@ -231,6 +231,7 @@ class ContactChatViewController: UITableViewController {
             self.navigationController?.navigationBar.topItem?.title = "Start Conversation".localized()
             self.navigationController?.navigationBar.setNeedsLayout()
         }
+        getData()
         DispatchQueue.global().async {
             self.getOpenGroups(listGroups: self.groups, completion: { g in
                 DispatchQueue.main.async {
@@ -669,7 +670,12 @@ extension ContactChatViewController {
                     dismiss(animated: true, completion: nil)
                     return
                 }
-                if data.messageScope == "3" {
+                if data.pin == "-997" {
+                    let smartChatVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "chatGptVC") as! ChatGPTBotView
+                    smartChatVC.hidesBottomBarWhenPushed = true
+                    smartChatVC.fromNotification = false
+                    navigationController?.show(smartChatVC, sender: nil)
+                } else if data.messageScope == "3" {
                     let editorPersonalVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorPersonalVC") as! EditorPersonal
                     editorPersonalVC.hidesBottomBarWhenPushed = true
                     editorPersonalVC.unique_l_pin = data.pin
@@ -1000,7 +1006,10 @@ extension ContactChatViewController {
         case 0:
             if segment.numberOfSegments < 3 {
                 cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifierContact", for: indexPath)
-                var content = cell.defaultContentConfiguration()
+                let content = cell.contentView
+                if content.subviews.count > 0 {
+                    content.subviews.forEach { $0.removeFromSuperview() }
+                }
                 let data: User
                 if isFilltering {
                     data = fillteredData[indexPath.row] as! User
@@ -1010,35 +1019,54 @@ extension ContactChatViewController {
                     }
                     data = contacts[indexPath.row]
                 }
-                content.imageProperties.maximumSize = CGSize(width: 40, height: 40)
+                let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 40.0, height: 40.0))
+                content.addSubview(imageView)
+                imageView.translatesAutoresizingMaskIntoConstraints = false
+                NSLayoutConstraint.activate([
+                    imageView.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 10.0),
+                    imageView.centerYAnchor.constraint(equalTo: content.centerYAnchor),
+                    imageView.widthAnchor.constraint(equalToConstant: 40.0),
+                    imageView.heightAnchor.constraint(equalToConstant: 40.0)
+                ])
                 if data.pin == "-997" {
-                    content.image = UIImage(named: "pb_gpt_bot", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
+                    imageView.circle()
+                    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
+                                imageView.animationDuration = image?.duration ?? 0.0
+                                imageView.animationRepeatCount = 0
+                                imageView.startAnimating()
+                            }
+                        }
+                    }
                 }
                 else {
                     getImage(name: data.thumb, placeholderImage: UIImage(named: "Profile---Purple", in: Bundle.resourceBundle(for: Nexilis.self), with: nil), isCircle: true, tableView: tableView, indexPath: indexPath, completion: { result, isDownloaded, image in
-                        content.image = image
+                        imageView.image = image
                     })
                 }
-                if User.isOfficial(official_account: data.official ?? "") || User.isOfficialRegular(official_account: data.official ?? "") {
-                    content.attributedText = self.set(image: UIImage(named: "ic_official_flag", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: "  \(data.fullName)", size: 15, y: -4, colorText: UIColor.officialColor)
-                    
+                let titleView = UILabel()
+                content.addSubview(titleView)
+                titleView.translatesAutoresizingMaskIntoConstraints = false
+                NSLayoutConstraint.activate([
+                    titleView.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 10.0),
+                    titleView.centerYAnchor.constraint(equalTo: content.centerYAnchor)
+                ])
+                titleView.font = UIFont.systemFont(ofSize: 14)
+                if (User.isOfficial(official_account: data.official ?? "") || User.isOfficialRegular(official_account: data.official ?? "")) && data.pin != "-997" {
+                    titleView.attributedText = self.set(image: UIImage(named: "ic_official_flag", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: "  \(data.fullName)", size: 15, y: -4, colorText: UIColor.officialColor)
+
                 } else if User.isVerified(official_account: data.official ?? "") {
-                    content.attributedText = self.set(image: UIImage(named: "ic_verified", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: "  \(data.fullName)", size: 15, y: -4, colorText: UIColor.verifiedColor)
+                    titleView.attributedText = self.set(image: UIImage(named: "ic_verified", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: "  \(data.fullName)", size: 15, y: -4, colorText: UIColor.verifiedColor)
                 }
                 else if User.isInternal(userType: data.userType ?? "") {
-                    content.attributedText = self.set(image: UIImage(named: "ic_internal", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: "  \(data.fullName)", size: 15, y: -4, colorText: UIColor.internalColor)
+                    titleView.attributedText = self.set(image: UIImage(named: "ic_internal", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: "  \(data.fullName)", size: 15, y: -4, colorText: UIColor.internalColor)
                 } else if User.isCallCenter(userType: data.userType ?? "") {
-//                    let dataCategory = CategoryCC.getDataFromServiceId(service_id: data.ex_offmp!)
-//                    if dataCategory != nil {
-//                        content.attributedText = self.set(image: UIImage(named: "pb_call_center", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: "  \(data.fullName) (\(dataCategory!.service_name))", size: 15, y: -4, colorText: UIColor.ccColor)
-//                    } else {
-                        content.attributedText = self.set(image: UIImage(named: "pb_call_center", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: "  \(data.fullName)", size: 15, y: -4, colorText: UIColor.ccColor)
-//                    }
+                    titleView.attributedText = self.set(image: UIImage(named: "pb_call_center", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: "  \(data.fullName)", size: 15, y: -4, colorText: UIColor.ccColor)
                 } else {
-                    content.text = data.fullName
+                    titleView.text = data.fullName
                 }
-                content.textProperties.font = UIFont.systemFont(ofSize: 14)
-                cell.contentConfiguration = content
             } else {
                 cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifierChat", for: indexPath)
                 let content = cell.contentView
@@ -1083,16 +1111,50 @@ extension ContactChatViewController {
                     imageView.widthAnchor.constraint(equalToConstant: 55.0),
                     imageView.heightAnchor.constraint(equalToConstant: 55.0)
                 ])
-                if data.profile.isEmpty && data.pin != "-999" {
+                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)
                     } else {
                         imageView.image = UIImage(named: "Conversation---Purple", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
                     }
+                } 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") {
+                        imageView.sd_setImage(with: urlGif) { (image, error, cacheType, imageURL) in
+                            if error == nil {
+                                imageView.animationImages = image?.images
+                                imageView.animationDuration = image?.duration ?? 0.0
+                                imageView.animationRepeatCount = 0
+                                imageView.startAnimating()
+                            }
+                        }
+                    }
                 } 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
-                        imageView.image = image
-                    })
+                    if !Utils.getIconDock().isEmpty {
+                        let urlString = Utils.getUrlDock()!
+                        if let cachedImage = ImageCache.shared.image(forKey: urlString) {
+                            let imageData = cachedImage
+                            imageView.image = imageData
+                        } else {
+                            DispatchQueue.global().async{
+                                Utils.fetchDataWithCookiesAndUserAgent(from: URL(string: urlString)!) { data, response, error in
+                                    guard let data = data, error == nil else { return }
+                                    DispatchQueue.main.async() {
+                                        if UIImage(data: data) != nil {
+                                            let imageData = UIImage(data: data)!
+                                            imageView.image = imageData
+                                            ImageCache.shared.save(image: imageData, forKey: urlString)
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    } 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
+                            imageView.image = image
+                        })
+                    }
                 }
                 let titleView = UILabel()
                 content.addSubview(titleView)
@@ -1315,7 +1377,10 @@ extension ContactChatViewController {
                 }
             } else {
                 cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifierContact", for: indexPath)
-                var content = cell.defaultContentConfiguration()
+                let content = cell.contentView
+                if content.subviews.count > 0 {
+                    content.subviews.forEach { $0.removeFromSuperview() }
+                }
                 let data: User
                 if isFilltering {
                     data = fillteredData[indexPath.row] as! User
@@ -1325,34 +1390,54 @@ extension ContactChatViewController {
                     }
                     data = contacts[indexPath.row]
                 }
-                content.imageProperties.maximumSize = CGSize(width: 40, height: 40)
+                let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 40.0, height: 40.0))
+                content.addSubview(imageView)
+                imageView.translatesAutoresizingMaskIntoConstraints = false
+                NSLayoutConstraint.activate([
+                    imageView.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 10.0),
+                    imageView.centerYAnchor.constraint(equalTo: content.centerYAnchor),
+                    imageView.widthAnchor.constraint(equalToConstant: 40.0),
+                    imageView.heightAnchor.constraint(equalToConstant: 40.0)
+                ])
                 if data.pin == "-997" {
-                    content.image = UIImage(named: "pb_gpt_bot", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
-                } else {
+                    imageView.circle()
+                    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
+                                imageView.animationDuration = image?.duration ?? 0.0
+                                imageView.animationRepeatCount = 0
+                                imageView.startAnimating()
+                            }
+                        }
+                    }
+                }
+                else {
                     getImage(name: data.thumb, placeholderImage: UIImage(named: "Profile---Purple", in: Bundle.resourceBundle(for: Nexilis.self), with: nil), isCircle: true, tableView: tableView, indexPath: indexPath, completion: { result, isDownloaded, image in
-                        content.image = image
+                        imageView.image = image
                     })
                 }
-                if User.isOfficial(official_account: data.official ?? "") || User.isOfficialRegular(official_account: data.official ?? "") {
-                    content.attributedText = self.set(image: UIImage(named: "ic_official_flag", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: "  \(data.fullName)", size: 15, y: -4, colorText: UIColor.officialColor)
-                    
+                let titleView = UILabel()
+                content.addSubview(titleView)
+                titleView.translatesAutoresizingMaskIntoConstraints = false
+                NSLayoutConstraint.activate([
+                    titleView.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 10.0),
+                    titleView.centerYAnchor.constraint(equalTo: content.centerYAnchor)
+                ])
+                titleView.font = UIFont.systemFont(ofSize: 14)
+                if (User.isOfficial(official_account: data.official ?? "") || User.isOfficialRegular(official_account: data.official ?? "")) && data.pin != "-997" {
+                    titleView.attributedText = self.set(image: UIImage(named: "ic_official_flag", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: "  \(data.fullName)", size: 15, y: -4, colorText: UIColor.officialColor)
+
                 } else if User.isVerified(official_account: data.official ?? "") {
-                    content.attributedText = self.set(image: UIImage(named: "ic_verified", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: "  \(data.fullName)", size: 15, y: -4, colorText: UIColor.verifiedColor)
+                    titleView.attributedText = self.set(image: UIImage(named: "ic_verified", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: "  \(data.fullName)", size: 15, y: -4, colorText: UIColor.verifiedColor)
                 }
                 else if User.isInternal(userType: data.userType ?? "") {
-                    content.attributedText = self.set(image: UIImage(named: "ic_internal", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: "  \(data.fullName)", size: 15, y: -4, colorText: UIColor.internalColor)
+                    titleView.attributedText = self.set(image: UIImage(named: "ic_internal", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: "  \(data.fullName)", size: 15, y: -4, colorText: UIColor.internalColor)
                 } else if User.isCallCenter(userType: data.userType ?? "") {
-//                    let dataCategory = CategoryCC.getDataFromServiceId(service_id: data.ex_offmp!)
-//                    if dataCategory != nil {
-//                        content.attributedText = self.set(image: UIImage(named: "pb_call_center", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: "  \(data.fullName) (\(dataCategory!.service_name))", size: 15, y: -4, colorText: UIColor.ccColor)
-//                    } else {
-                        content.attributedText = self.set(image: UIImage(named: "pb_call_center", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: "  \(data.fullName)", size: 15, y: -4, colorText: UIColor.ccColor)
-//                    }
+                    titleView.attributedText = self.set(image: UIImage(named: "pb_call_center", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: "  \(data.fullName)", size: 15, y: -4, colorText: UIColor.ccColor)
                 } else {
-                    content.text = data.fullName
+                    titleView.text = data.fullName
                 }
-                content.textProperties.font = UIFont.systemFont(ofSize: 14)
-                cell.contentConfiguration = content
             }
         case 2:
             cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifierGroup", for: indexPath)

+ 22 - 3
NexilisLite/NexilisLite/Source/View/Control/GroupCreateViewController.swift

@@ -10,6 +10,7 @@ import UIKit
 class GroupCreateViewController: UITableViewController {
 
     @IBOutlet weak var name: UITextField!
+    @IBOutlet weak var subGroup: UITextField!
     
     private let id = Date().currentTimeMillis().toHex()
     
@@ -31,7 +32,8 @@ class GroupCreateViewController: UITableViewController {
         navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(cancel(sender:)))
         navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Save".localized(), style: .done, target: self, action: #selector(save(sender:)))
         navigationItem.rightBarButtonItem?.isEnabled = false
-        name.placeholder = "Name".localized()
+        name.placeholder = "Title".localized() + "*"
+        subGroup.placeholder = "Sub Group".localized() + " " + "(" + "Optional".localized() + ")"
         
         name.addTarget(self, action: #selector(onTextChanged(sender:)), for: .editingChanged)
     }
@@ -60,11 +62,28 @@ class GroupCreateViewController: UITableViewController {
     }
     
     @objc func save(sender: Any) {
+        Nexilis.showLoader()
         submit { result in
             DispatchQueue.main.async {
                 if result {
-                    self.navigationController?.dismiss(animated: true, completion: nil)
-                    self.isDismiss?(self.id)
+                    if let subGroup = self.subGroup.text, !subGroup.isEmpty {
+                        DispatchQueue.global().async {
+                            let idSub = Date().currentTimeMillis().toHex()
+                            if let result = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getCreateSubGroup(group_id: idSub, group_name: subGroup, parent_id: self.id, level: "2")), result.isOk() {
+                                DispatchQueue.main.async {
+                                    Nexilis.hideLoader {
+                                        self.navigationController?.dismiss(animated: true, completion: nil)
+                                        self.isDismiss?(self.id)
+                                    }
+                                }
+                            }
+                        }
+                    } else {
+                        Nexilis.hideLoader {
+                            self.navigationController?.dismiss(animated: true, completion: nil)
+                            self.isDismiss?(self.id)
+                        }
+                    }
                 } else {
                     self.view.makeToast("Server busy, please try again later".localized(), duration: 3)
                 }

+ 16 - 7
NexilisLite/NexilisLite/Source/View/Control/HistoryBroadcastViewController.swift

@@ -149,14 +149,23 @@ class HistoryBroadcastViewController: UIViewController, UITableViewDelegate, UIT
                 }
             } else {
                 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 {
-                        getImage(name: data.profile, placeholderImage: UIImage(data: dataImage!), isCircle: true, tableView: tableView, indexPath: indexPath, completion: { result, isDownloaded, image in
-                            imageView.image = image
-                            if !result {
-                                imageView.tintColor = .mainColor
+                    let urlString = Utils.getUrlDock()!
+                    if let cachedImage = ImageCache.shared.image(forKey: urlString) {
+                        let imageData = cachedImage
+                        imageView.image = imageData
+                    } else {
+                        DispatchQueue.global().async{
+                            Utils.fetchDataWithCookiesAndUserAgent(from: URL(string: urlString)!) { data, response, error in
+                                guard let data = data, error == nil else { return }
+                                DispatchQueue.main.async() {
+                                    if UIImage(data: data) != nil {
+                                        let imageData = UIImage(data: data)!
+                                        imageView.image = imageData
+                                        ImageCache.shared.save(image: imageData, forKey: urlString)
+                                    }
+                                }
                             }
-                        })
+                        }
                     }
                 } else {
                     getImage(name: data.profile, placeholderImage: UIImage(named: data.pin == "-999" ? "pb_button" : "Conversation---Purple", in: Bundle.resourceBundle(for: Nexilis.self), with: nil), isCircle: true, tableView: tableView, indexPath: indexPath, completion: { result, isDownloaded, image in

+ 1 - 1
NexilisLite/NexilisLite/Source/View/Streaming/QmeraCreateStreamingViewController.swift

@@ -323,7 +323,7 @@ public class QmeraCreateStreamingViewController: UITableViewController {
                     return
                 }
                 
-                if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.createLS(title: "1~\(data["title"] ?? "")", type: data["type"] as! String, category: "3", tagline: data["tagline"] as! String, notifType: data["broadcast_type"] as! String, blogId: data["blog"] as! String, data: json)) {
+                if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.createLS(title: "0~\(data["title"] ?? "")", type: data["type"] as! String, category: "3", tagline: data["tagline"] as! String, notifType: data["broadcast_type"] as! String, blogId: data["blog"] as! String, data: json)) {
                     if response.getBody(key: CoreMessage_TMessageKey.ERRCOD) != "00" {
                         let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
                         imageView.tintColor = .white

+ 7 - 7
NexilisLite/NexilisLite/Source/View/Streaming/QmeraStreamingViewController.swift

@@ -260,7 +260,7 @@ class QmeraStreamingViewController: UIViewController {
             buttonRotate.addTarget(self, action: #selector(camera(sender:)), for: .touchUpInside)
             navigationItem.rightBarButtonItem = UIBarButtonItem(customView: buttonRotate)
             
-            API.initiateBC(sTitle: data, nCamIdx: 1, nResIdx: 2, nVQuality: 4, ivLocalView: imageView)
+            API.initiateBC(sTitle: data, nCamIdx: 0, nResIdx: 2, nVQuality: 4, ivLocalView: imageView)
         } else {
             API.joinBC(sBroadcasterID: data, ivRemoteView: imageView)
         }
@@ -399,7 +399,7 @@ class QmeraStreamingViewController: UIViewController {
                         let titleWithChar = titleRaw.components(separatedBy: "~")[1]
                         let title = titleWithChar.components(separatedBy: "■")[0]
                         
-                        self.status.text = title
+                        self.status.text = title.toNormalString()
                         let size = self.status.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: self.status.frame.height))
                         let width = size.width
                         self.status.frame = CGRect(x: 0, y: 0, width: width + 10, height: 30)
@@ -494,7 +494,7 @@ class QmeraStreamingViewController: UIViewController {
                     return
                 }
                 
-                if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.getUpdateLiveVideo(pTitle: type == 0 ? "\(self.frontCamera ? 1 : 0)~\(textField!.text!.toStupidString())" : "\(self.frontCamera ? 1 : 0)~\(self.status.text!.toStupidString())", pTagline: type == 1 ? textField!.text! : self.tagline.text!.toStupidString())) {
+                if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.getUpdateLiveVideo(pTitle: type == 0 ? "\(self.frontCamera ? 1 : 0)~\(textField!.text!.toStupidString())" : "\(self.frontCamera ? 1 : 0)~\(self.status.text!.toStupidString())", pTagline: type == 1 ? textField!.text! : self.tagline.text!.toStupidString())) {
                     if response.isOk() {
                         if type == 0 {
                             self.status.text = textField!.text!
@@ -637,11 +637,11 @@ extension QmeraStreamingViewController: LiveStreamingDelegate {
             let platform = Int(m[3])
             if platform == 1 { // Android
                 DispatchQueue.main.async {
-                    self.imageView.transform = CGAffineTransform.init(scaleX: 1.9, y: 1.9).rotated(by: camera == 1 ? (CGFloat.pi * 3)/2 : (CGFloat.pi)/2)
+                    self.imageView.transform = CGAffineTransform.init(scaleX: 1.9, y: 2.2).rotated(by: camera == 1 ? (CGFloat.pi * 3)/2 : (CGFloat.pi)/2)
                 }
             } else {
                 DispatchQueue.main.async {
-                    self.imageView.transform = CGAffineTransform.init(scaleX: 1.9, y: 1.9).rotated(by: camera == 1 ? (CGFloat.pi * 5)/2 : (CGFloat.pi)/2)
+                    self.imageView.transform = CGAffineTransform.init(scaleX: 1.9, y: 2.2).rotated(by: camera == 1 ? (CGFloat.pi * 5)/2 : (CGFloat.pi)/2)
                 }
             }
             sendJoin()
@@ -653,11 +653,11 @@ extension QmeraStreamingViewController: LiveStreamingDelegate {
             let platform = Int(m[3])
             if platform == 1 { // Android
                 DispatchQueue.main.async {
-                    self.imageView.transform = CGAffineTransform.init(scaleX: 1.9, y: 1.9).rotated(by: camera == 1 ? (CGFloat.pi * 3)/2 : (CGFloat.pi)/2)
+                    self.imageView.transform = CGAffineTransform.init(scaleX: 1.9, y: 2.2).rotated(by: camera == 1 ? (CGFloat.pi * 3)/2 : (CGFloat.pi)/2)
                 }
             } else {
                 DispatchQueue.main.async {
-                    self.imageView.transform = CGAffineTransform.init(scaleX: 1.9, y: 1.9).rotated(by: camera == 1 ? (CGFloat.pi * 5)/2 : (CGFloat.pi)/2)
+                    self.imageView.transform = CGAffineTransform.init(scaleX: 1.9, y: 2.2).rotated(by: camera == 1 ? (CGFloat.pi * 5)/2 : (CGFloat.pi)/2)
                 }
             }
         } else if state == Nexilis.STREAMING_SEMINAR_ENDED {