Forráskód Böngészése

update fix bugs and tuning

alqindiirsyam 6 hónapja
szülő
commit
2ebb182189
28 módosított fájl, 518 hozzáadás és 230 törlés
  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 {