Pārlūkot izejas kodu

Add New Features and update fix bugs

alqindiirsyam 7 mēneši atpakaļ
vecāks
revīzija
278e8d3046

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

@@ -540,7 +540,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 4.0.5;
+				MARKETING_VERSION = 4.0.6;
 				PRODUCT_BUNDLE_IDENTIFIER = io.newuniverse.AppBuilder2;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
@@ -572,7 +572,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 4.0.5;
+				MARKETING_VERSION = 4.0.6;
 				PRODUCT_BUNDLE_IDENTIFIER = io.newuniverse.AppBuilder2;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";

+ 36 - 24
AppBuilder/AppBuilder/Base.lproj/Main.storyboard

@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="nD6-T3-59p">
+<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" initialViewController="nD6-T3-59p">
     <device id="retina6_7" orientation="portrait" appearance="dark"/>
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
         <capability name="Safe area layout guides" minToolsVersion="9.0"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
@@ -13,7 +13,7 @@
             <objects>
                 <viewController id="yL2-sh-r2b" sceneMemberID="viewController">
                     <view key="view" contentMode="scaleToFill" id="Cwo-Ej-asl">
-                        <rect key="frame" x="0.0" y="0.0" width="428" height="926"/>
+                        <rect key="frame" x="0.0" y="0.0" width="430" height="932"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <viewLayoutGuide key="safeArea" id="NCr-Sf-zM1"/>
                         <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -29,7 +29,7 @@
             <objects>
                 <viewController storyboardIdentifier="firstTabVC" useStoryboardIdentifierAsRestorationIdentifier="YES" id="iKy-YH-N88" customClass="FirstTabViewController" customModule="AppBuilder" customModuleProvider="target" sceneMemberID="viewController">
                     <view key="view" contentMode="scaleToFill" id="OUO-5T-AtV">
-                        <rect key="frame" x="0.0" y="0.0" width="428" height="926"/>
+                        <rect key="frame" x="0.0" y="0.0" width="430" height="932"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <viewLayoutGuide key="safeArea" id="f2x-TV-t0G"/>
                     </view>
@@ -43,32 +43,39 @@
             <objects>
                 <viewController storyboardIdentifier="secondTabVC" useStoryboardIdentifierAsRestorationIdentifier="YES" id="z6f-Hz-Xwh" customClass="SecondTabViewController" customModule="AppBuilder" customModuleProvider="target" sceneMemberID="viewController">
                     <view key="view" contentMode="scaleToFill" id="T1X-gt-fSN">
-                        <rect key="frame" x="0.0" y="0.0" width="428" height="926"/>
+                        <rect key="frame" x="0.0" y="0.0" width="430" height="932"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <subviews>
                             <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="BU2-P5-16y">
-                                <rect key="frame" x="0.0" y="0.0" width="428" height="926"/>
+                                <rect key="frame" x="0.0" y="0.0" width="430" height="932"/>
                             </imageView>
                             <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="oT8-Uw-cgS">
-                                <rect key="frame" x="0.0" y="47" width="428" height="845"/>
+                                <rect key="frame" x="0.0" y="59" width="430" height="839"/>
                                 <subviews>
+                                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="PgW-Nv-Scf">
+                                        <rect key="frame" x="0.0" y="0.0" width="430" height="45"/>
+                                        <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                        <constraints>
+                                            <constraint firstAttribute="height" constant="45" id="VqH-jB-6ui"/>
+                                        </constraints>
+                                    </view>
                                     <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="4Jc-b8-aeF">
-                                        <rect key="frame" x="0.0" y="0.0" width="428" height="845"/>
+                                        <rect key="frame" x="0.0" y="45" width="430" height="794"/>
                                         <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                         <prototypes>
                                             <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="reuseIdentifierChat" id="Tz5-0s-zFj">
-                                                <rect key="frame" x="0.0" y="50" width="428" height="43.666667938232422"/>
+                                                <rect key="frame" x="0.0" y="50" width="430" height="43.666667938232422"/>
                                                 <autoresizingMask key="autoresizingMask"/>
                                                 <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Tz5-0s-zFj" id="4MW-eb-afV">
-                                                    <rect key="frame" x="0.0" y="0.0" width="428" height="43.666667938232422"/>
+                                                    <rect key="frame" x="0.0" y="0.0" width="430" height="43.666667938232422"/>
                                                     <autoresizingMask key="autoresizingMask"/>
                                                 </tableViewCellContentView>
                                             </tableViewCell>
                                             <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="reuseIdentifierGroup" id="im7-ks-lTd">
-                                                <rect key="frame" x="0.0" y="93.666667938232422" width="428" height="43.666667938232422"/>
+                                                <rect key="frame" x="0.0" y="93.666667938232422" width="430" height="43.666667938232422"/>
                                                 <autoresizingMask key="autoresizingMask"/>
                                                 <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="im7-ks-lTd" id="lNo-4T-eyr">
-                                                    <rect key="frame" x="0.0" y="0.0" width="428" height="43.666667938232422"/>
+                                                    <rect key="frame" x="0.0" y="0.0" width="430" height="43.666667938232422"/>
                                                     <autoresizingMask key="autoresizingMask"/>
                                                 </tableViewCellContentView>
                                             </tableViewCell>
@@ -83,8 +90,11 @@
                                 <constraints>
                                     <constraint firstAttribute="bottom" secondItem="4Jc-b8-aeF" secondAttribute="bottom" id="AJg-3y-ycq"/>
                                     <constraint firstAttribute="trailing" secondItem="4Jc-b8-aeF" secondAttribute="trailing" id="MQg-wg-wJC"/>
+                                    <constraint firstItem="4Jc-b8-aeF" firstAttribute="top" secondItem="PgW-Nv-Scf" secondAttribute="bottom" id="ZUd-zw-4hy"/>
                                     <constraint firstItem="4Jc-b8-aeF" firstAttribute="leading" secondItem="oT8-Uw-cgS" secondAttribute="leading" id="gr8-uB-Kjv"/>
-                                    <constraint firstItem="4Jc-b8-aeF" firstAttribute="top" secondItem="oT8-Uw-cgS" secondAttribute="top" id="tgW-8t-y1Q"/>
+                                    <constraint firstItem="PgW-Nv-Scf" firstAttribute="leading" secondItem="oT8-Uw-cgS" secondAttribute="leading" id="npT-Gf-5EX"/>
+                                    <constraint firstItem="PgW-Nv-Scf" firstAttribute="top" secondItem="oT8-Uw-cgS" secondAttribute="top" id="od8-UY-wE5"/>
+                                    <constraint firstAttribute="trailing" secondItem="PgW-Nv-Scf" secondAttribute="trailing" id="yEI-vY-L8E"/>
                                 </constraints>
                             </view>
                         </subviews>
@@ -103,7 +113,9 @@
                     </view>
                     <connections>
                         <outlet property="backgroundImage" destination="BU2-P5-16y" id="gud-Yi-EyH"/>
+                        <outlet property="heightToolbar" destination="VqH-jB-6ui" id="pWl-cH-S6S"/>
                         <outlet property="tableView" destination="4Jc-b8-aeF" id="kie-X3-tsw"/>
+                        <outlet property="viewToolbar" destination="PgW-Nv-Scf" id="z5k-Qe-iDz"/>
                     </connections>
                 </viewController>
                 <placeholder placeholderIdentifier="IBFirstResponder" id="iEb-Pf-p13" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
@@ -115,7 +127,7 @@
             <objects>
                 <viewController id="shx-A0-Rpw" sceneMemberID="viewController">
                     <view key="view" contentMode="scaleToFill" id="0VY-c7-1YL">
-                        <rect key="frame" x="0.0" y="0.0" width="428" height="926"/>
+                        <rect key="frame" x="0.0" y="0.0" width="430" height="932"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <viewLayoutGuide key="safeArea" id="cTG-dq-upN"/>
                         <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -131,7 +143,7 @@
             <objects>
                 <navigationController title="test" id="nD6-T3-59p" sceneMemberID="viewController">
                     <navigationBar key="navigationBar" opaque="NO" contentMode="scaleToFill" id="mEs-FV-nQc">
-                        <rect key="frame" x="0.0" y="47" width="428" height="44"/>
+                        <rect key="frame" x="0.0" y="59" width="430" height="44"/>
                         <autoresizingMask key="autoresizingMask"/>
                         <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                         <color key="tintColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -181,7 +193,7 @@
             <objects>
                 <viewController id="BQG-BK-XdT" userLabel="Item 3" sceneMemberID="viewController">
                     <view key="view" contentMode="scaleToFill" id="tfu-1X-nbl">
-                        <rect key="frame" x="0.0" y="0.0" width="428" height="926"/>
+                        <rect key="frame" x="0.0" y="0.0" width="430" height="932"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <viewLayoutGuide key="safeArea" id="5ym-td-Khs"/>
                         <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -197,7 +209,7 @@
             <objects>
                 <viewController id="HRs-yQ-che" userLabel="Item 4" sceneMemberID="viewController">
                     <view key="view" contentMode="scaleToFill" id="CpP-4M-jQO">
-                        <rect key="frame" x="0.0" y="0.0" width="428" height="926"/>
+                        <rect key="frame" x="0.0" y="0.0" width="430" height="932"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <viewLayoutGuide key="safeArea" id="FW7-Pt-spm"/>
                         <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -213,7 +225,7 @@
             <objects>
                 <viewController id="doo-wB-Xef" userLabel="Item 5" sceneMemberID="viewController">
                     <view key="view" contentMode="scaleToFill" id="gva-IL-Ua7">
-                        <rect key="frame" x="0.0" y="0.0" width="428" height="926"/>
+                        <rect key="frame" x="0.0" y="0.0" width="430" height="932"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <viewLayoutGuide key="safeArea" id="b9A-7D-aDQ"/>
                         <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -229,7 +241,7 @@
             <objects>
                 <viewController storyboardIdentifier="thirdTabVC" useStoryboardIdentifierAsRestorationIdentifier="YES" id="sim-nJ-liW" customClass="ThirdTabViewController" customModule="AppBuilder" customModuleProvider="target" sceneMemberID="viewController">
                     <view key="view" contentMode="scaleToFill" id="zlx-Nr-4qk">
-                        <rect key="frame" x="0.0" y="0.0" width="428" height="926"/>
+                        <rect key="frame" x="0.0" y="0.0" width="430" height="932"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <viewLayoutGuide key="safeArea" id="gQh-2u-SN6"/>
                     </view>
@@ -243,23 +255,23 @@
             <objects>
                 <viewController storyboardIdentifier="fourthTabVC" useStoryboardIdentifierAsRestorationIdentifier="YES" id="ruc-qb-tdV" customClass="FourthTabViewController" customModule="AppBuilder" customModuleProvider="target" sceneMemberID="viewController">
                     <view key="view" contentMode="scaleToFill" id="9o3-Cd-xmC">
-                        <rect key="frame" x="0.0" y="0.0" width="428" height="926"/>
+                        <rect key="frame" x="0.0" y="0.0" width="430" height="932"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <subviews>
                             <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="rdi-Bn-gaF">
-                                <rect key="frame" x="0.0" y="0.0" width="428" height="926"/>
+                                <rect key="frame" x="0.0" y="0.0" width="430" height="932"/>
                             </imageView>
                             <tableView opaque="NO" contentMode="scaleToFill" alwaysBounceVertical="YES" bouncesZoom="NO" dataMode="prototypes" style="insetGrouped" separatorStyle="default" separatorInsetReference="fromAutomaticInsets" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="5" sectionFooterHeight="5" translatesAutoresizingMaskIntoConstraints="NO" id="gUO-Ws-n00">
-                                <rect key="frame" x="0.0" y="80" width="428" height="807"/>
+                                <rect key="frame" x="0.0" y="80" width="430" height="813"/>
                                 <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                 <inset key="separatorInset" minX="3" minY="0.0" maxX="0.0" maxY="0.0"/>
                                 <color key="sectionIndexBackgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                 <prototypes>
                                     <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="2" reuseIdentifier="reuseIdentifier" id="IEo-SX-4um">
-                                        <rect key="frame" x="20" y="55.333332061767578" width="388" height="43.666667938232422"/>
+                                        <rect key="frame" x="20" y="55.333332061767578" width="390" height="43.666667938232422"/>
                                         <autoresizingMask key="autoresizingMask"/>
                                         <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="left" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="IEo-SX-4um" id="je4-kr-tvF">
-                                            <rect key="frame" x="0.0" y="0.0" width="388" height="43.666667938232422"/>
+                                            <rect key="frame" x="0.0" y="0.0" width="390" height="43.666667938232422"/>
                                             <autoresizingMask key="autoresizingMask"/>
                                             <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                             <edgeInsets key="layoutMargins" top="0.0" left="0.0" bottom="0.0" right="0.0"/>

+ 115 - 2
AppBuilder/AppBuilder/FirstTabViewController.swift

@@ -11,7 +11,7 @@ import NexilisLite
 import Speech
 import CommonCrypto
 
-class FirstTabViewController: UIViewController, UIScrollViewDelegate, UIGestureRecognizerDelegate, WKScriptMessageHandler {
+class FirstTabViewController: UIViewController, UIScrollViewDelegate, UIGestureRecognizerDelegate, WKScriptMessageHandler, ImageVideoPickerDelegate {
     
     var webView: WKWebView!
     var address = ""
@@ -31,6 +31,9 @@ class FirstTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
     public static var atFirstPage = true
     public static var showModal = false
     
+    var indexImageVideoWv = 0
+    var imageVideoPicker: ImageVideoPicker!
+    
     override func viewDidLoad() {
         super.viewDidLoad()
         
@@ -70,6 +73,7 @@ class FirstTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
         contentController.add(self, name: "closeProfile")
         contentController.add(self, name: "tabShowHide")
         contentController.add(self, name: "shareText")
+        contentController.add(self, name: "openGalleryiOS")
         
         let source: String = "var meta = document.createElement('meta');" +
             "meta.name = 'viewport';" +
@@ -187,6 +191,7 @@ class FirstTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
                             viewController.tabBar.isHidden = false
                             ViewController.middleButton.isHidden = false
                             ViewController.alwaysHideButton = false
+                            viewController.indicatorImage.isHidden = false
                         }
                     }
                 } else if PrefsUtil.getCpaasMode() != PrefsUtil.CPAAS_MODE_DOCKED {
@@ -195,6 +200,7 @@ class FirstTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
                             if viewController.tabBar.isHidden {
                                 viewController.tabBar.isHidden = false
                                 ViewController.alwaysHideButton = false
+                                viewController.indicatorImage.isHidden = false
                             }
                         }
                     }
@@ -256,6 +262,7 @@ class FirstTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
                 ViewController.hideDockedButton()
                 if let viewController = viewController as? ViewController {
                     viewController.tabBar.isHidden = true
+                    viewController.indicatorImage.isHidden = true
                 }
                 ViewController.removeMiddleButton()
             }
@@ -264,6 +271,7 @@ class FirstTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
                 if let viewController = viewController as? ViewController {
                     if !viewController.tabBar.isHidden {
                         viewController.tabBar.isHidden = true
+                        viewController.indicatorImage.isHidden = true
                     }
                 }
             }
@@ -282,12 +290,14 @@ class FirstTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
             if let viewController = viewController as? ViewController {
                 viewController.tabBar.isHidden = false
                 ViewController.middleButton.isHidden = false
+                viewController.indicatorImage.isHidden = false
             }
         } else if PrefsUtil.getCpaasMode() != PrefsUtil.CPAAS_MODE_DOCKED {
             DispatchQueue.main.async {
                 if let viewController = viewController as? ViewController {
                     if viewController.tabBar.isHidden {
                         viewController.tabBar.isHidden = false
+                        viewController.indicatorImage.isHidden = false
                     }
                 }
             }
@@ -436,13 +446,116 @@ class FirstTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
                 }
             }
         } else if message.name == "shareText" {
-            print("HMM masuk share text")
             guard let dict = message.body as? [String: AnyObject],
                   let param1 = dict["param1"] as? String else {
                 return
             }
             let activityViewController = UIActivityViewController(activityItems: [param1], applicationActivities: nil)
             self.present(activityViewController, animated: true, completion: nil)
+        } else if message.name == "openGalleryiOS" {
+            guard let dict = message.body as? [String: AnyObject],
+                  let param1 = dict["param1"] as? Int else {
+                return
+            }
+            indexImageVideoWv = param1
+            let alertController = LibAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
+            
+            if let action = self.actionImageVideo(for: "image", title: "Choose Photo".localized()) {
+                alertController.addAction(action)
+            }
+            if let action = self.actionImageVideo(for: "video", title: "Choose Video".localized()) {
+                alertController.addAction(action)
+            }
+            alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: nil))
+            self.present(alertController, animated: true)
+        }
+    }
+    
+    private func actionImageVideo(for type: String, title: String) -> UIAlertAction? {
+        return UIAlertAction(title: title, style: .default) { [unowned self] _ in
+            switch type {
+            case "image":
+                imageVideoPicker.present(source: .imageAlbum)
+            case "video":
+                imageVideoPicker.present(source: .videoAlbum)
+            default:
+                imageVideoPicker.present(source: .imageAlbum)
+            }
+        }
+    }
+    
+    func didSelect(imagevideo: Any?) {
+        if imagevideo != nil {
+            let imageData = imagevideo! as! [UIImagePickerController.InfoKey : Any]
+            if (imageData[.mediaType] as! String == "public.image") {
+                let compressedImage = (imageData[.originalImage] as! UIImage).pngData()!
+                let base64String = compressedImage.base64EncodedString()
+                let base64ToWeb = "data:image/jpeg;base64,\(base64String)"
+                webView.evaluateJavaScript("loadFromMobile('\(base64ToWeb)',\(indexImageVideoWv))") { (result, error) in
+                    if let error = error {
+                        print("Error executing JavaScript: \(error)")
+                    }
+                }
+            } else {
+                guard var dataVideo = try? Data(contentsOf: imageData[.mediaURL] as! URL) else {
+                    return
+                }
+                let sizeOfVideo = Double(dataVideo.count / 1048576)
+                if (sizeOfVideo > 10.0) {
+                    let compressedURL = NSURL.fileURL(withPath: NSTemporaryDirectory() + UUID().uuidString + ".mp4")
+                    compressVideo(inputURL: imageData[.mediaURL] as! URL,
+                                  outputURL: compressedURL) { exportSession in
+                        guard let session = exportSession else {
+                            return
+                        }
+                        
+                        switch session.status {
+                        case .unknown:
+                            break
+                        case .waiting:
+                            break
+                        case .exporting:
+                            break
+                        case .completed:
+                            guard let compressedData = try? Data(contentsOf: compressedURL) else {
+                                return
+                            }
+                            dataVideo = compressedData
+                        case .failed:
+                            break
+                        case .cancelled:
+                            break
+                        @unknown default:
+                            break
+                        }
+                    }
+                }
+                let base64String = dataVideo.base64EncodedString()
+                let base64ToWeb = "data:video/mp4;base64,\(base64String)"
+                webView.evaluateJavaScript("loadFromMobile('\(base64ToWeb)',\(indexImageVideoWv))") { (result, error) in
+                    if let error = error {
+                        print("Error executing JavaScript: \(error)")
+                    }
+                }
+            }
+        }
+    }
+    
+    func compressVideo(inputURL: URL,
+                       outputURL: URL,
+                       handler:@escaping (_ exportSession: AVAssetExportSession?) -> Void) {
+        let urlAsset = AVURLAsset(url: inputURL, options: nil)
+        guard let exportSession = AVAssetExportSession(asset: urlAsset,
+                                                       presetName: AVAssetExportPresetMediumQuality) else {
+            handler(nil)
+            
+            return
+        }
+        
+        exportSession.outputURL = outputURL
+        exportSession.outputFileType = .mp4
+        exportSession.exportAsynchronously {
+            handler(exportSession)
         }
     }
     

+ 853 - 52
AppBuilder/AppBuilder/SecondTabViewController.swift

@@ -9,9 +9,9 @@ import UIKit
 import FMDB
 import NexilisLite
 import Speech
+import QuickLook
 
-class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGestureRecognizerDelegate, UITextFieldDelegate {
-    
+class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGestureRecognizerDelegate, UITextFieldDelegate, UICollectionViewDelegate, UICollectionViewDataSource, QLPreviewControllerDataSource {
     var isChooser: ((String, String) -> ())?
     
     var isAdmin: Bool = false
@@ -37,10 +37,30 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
     let textViewSearch = UITextField()
     let imageVoiceSb = UIImageView()
     let imageNewChatSb = UIImageView()
+    let imageClearSearch = UIImageView()
+    @IBOutlet weak var viewToolbar: UIView!
+    @IBOutlet weak var heightToolbar: NSLayoutConstraint!
+    var viewCategorySearch: UIScrollView!
+    var leftTVSearch: NSLayoutConstraint!
+    var viewCatInTV: UIView!
+    var imageViewSearch: UIImageView!
+    var gridImage: UICollectionView!
+    var previewItem: NSURL?
+    var audioPlayer: AVAudioPlayer?
     
-    override var preferredStatusBarStyle: UIStatusBarStyle {
-        return self.traitCollection.userInterfaceStyle == .dark ? .default : .lightContent // Change this to .default for black text color
-    }
+    let UNREAD_TAG = 10
+    let PHOTOS_TAG = 11
+    let DOCUMENTS_TAG = 12
+    let LINKS_TAG = 13
+    let VIDEOS_TAG = 14
+    let GIFS_TAG = 15
+    let AUDIOS_TAG = 16
+    
+    var selectedTag = 0
+    
+//    override var preferredStatusBarStyle: UIStatusBarStyle {
+//        return self.traitCollection.userInterfaceStyle == .dark ? .default : .lightContent // Change this to .default for black text color
+//    }
 
     lazy var searchController: UISearchController = {
         var searchController = UISearchController(searchResultsController: nil)
@@ -91,7 +111,56 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
             case 1:
                 fillteredData = self.groups.filter { $0.name.lowercased().contains(searchText.lowercased()) }
             default:
-                fillteredData = self.chats.filter { $0.name.lowercased().contains(searchText.lowercased()) || $0.messageText.lowercased().contains(searchText.lowercased()) }
+                if selectedTag == 0 {
+                    fillteredData = self.chats.filter { $0.name.lowercased().contains(searchText.lowercased()) || $0.messageText.lowercased().contains(searchText.lowercased()) }
+                } else {
+                    switch(selectedTag) {
+                    case UNREAD_TAG :
+                        fillteredData = self.chats.filter { $0.counter != "0" }
+                        break
+                    case PHOTOS_TAG, VIDEOS_TAG :
+                        fillteredData = Chat.getData(isImage: selectedTag == PHOTOS_TAG, isVideo: selectedTag == VIDEOS_TAG)
+                        if fillteredData.count > 0 {
+                            if gridImage != nil && gridImage.isDescendant(of: self.view) {
+                                gridImage.removeFromSuperview()
+                            }
+                            let width = self.view.frame.width / 3 - 2
+                            let cellSize = CGSize(width:width, height:width)
+                            let layout = UICollectionViewFlowLayout()
+                            layout.scrollDirection = .vertical
+                            layout.itemSize = cellSize
+                            layout.sectionInset = UIEdgeInsets(top: 1, left: 1, bottom: 1, right: 1)
+                            layout.minimumLineSpacing = 1.0
+                            layout.minimumInteritemSpacing = 1.0
+                            
+                            gridImage = UICollectionView(frame: .zero, collectionViewLayout: layout)
+                            self.view.addSubview(gridImage)
+                            gridImage.anchor(top: tableView.topAnchor, left: tableView.leftAnchor, bottom: tableView.bottomAnchor, right: tableView.rightAnchor)
+                            gridImage.backgroundColor = .clear
+                            
+                            gridImage.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "gridCell")
+
+                            gridImage.delegate = self
+                            gridImage.dataSource = self
+                            
+                            tableView.isHidden = true
+                        }
+                        break
+                    case DOCUMENTS_TAG :
+                        fillteredData = Chat.getData(isDoc: true)
+                        break
+                    case GIFS_TAG :
+                        break
+                    case LINKS_TAG :
+                        fillteredData = Chat.getData(isLink: true)
+                        break
+                    case AUDIOS_TAG :
+                        fillteredData = Chat.getData(isAudio: true)
+                        break
+                    default:
+                        break
+                    }
+                }
             }
         }
         tableView.reloadData()
@@ -141,7 +210,119 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
         NotificationCenter.default.addObserver(self, selector: #selector(onReloadTab(notification:)), name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil)
         NotificationCenter.default.addObserver(self, selector: #selector(onReloadTab(notification:)), name: NSNotification.Name(rawValue: "onTopic"), object: nil)
         
+        imageViewSearch = UIImageView(image: UIImage(named: self.traitCollection.userInterfaceStyle == .dark ? "nx_search_bar_dark" : "nx_search_bar", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!)
+        imageViewSearch.contentMode = .scaleToFill
+        viewToolbar.addSubview(imageViewSearch)
+        imageViewSearch.anchor(top: viewToolbar.topAnchor, left: viewToolbar.leftAnchor, right: viewToolbar.rightAnchor, paddingTop: 5, paddingLeft: 10, paddingRight: 10, height: 35)
+        imageViewSearch.isUserInteractionEnabled = true
+        
+        viewCategorySearch = UIScrollView()
+        viewCategorySearch.showsHorizontalScrollIndicator = false
+        viewToolbar.addSubview(viewCategorySearch)
+        viewCategorySearch.anchor(top: imageViewSearch.bottomAnchor, left: viewToolbar.leftAnchor, right: viewToolbar.rightAnchor, paddingLeft: 10, paddingRight: 10, height: 40)
+        viewCategorySearch.isHidden = true
+        
+        let groupViewCat = UIStackView()
+        viewCategorySearch.addSubview(groupViewCat)
+        groupViewCat.anchor(left: viewCategorySearch.leftAnchor, right: viewCategorySearch.rightAnchor, centerY: viewCategorySearch.centerYAnchor, height: 30)
+        groupViewCat.axis = .horizontal
+        groupViewCat.spacing = 10
+        
+        for i in 0..<7 {
+            var widthView: CGFloat = 105
+            var iconCat = UIImage(systemName: "bubble.right")
+            var textCat = "Unread".localized()
+            var tag = UNREAD_TAG
+            if i == 1 {
+                widthView = 100
+                iconCat = UIImage(systemName: "photo")
+                textCat = "Photos".localized()
+                tag = PHOTOS_TAG
+            } else if i == 2 {
+                widthView = 130
+                iconCat = UIImage(systemName: "doc")
+                textCat = "Documents".localized()
+                tag = DOCUMENTS_TAG
+            } else if i == 3 {
+                widthView = 80
+                iconCat = UIImage(systemName: "link")
+                textCat = "Links".localized()
+                tag = LINKS_TAG
+            } else if i == 4 {
+                widthView = 100
+                iconCat = UIImage(systemName: "video")
+                textCat = "Videos".localized()
+                tag = VIDEOS_TAG
+            } else if i == 5 {
+                widthView = 80
+                iconCat = UIImage(systemName: "photo.on.rectangle")
+                textCat = "GIFs".localized()
+                tag = GIFS_TAG
+            } else if i == 6 {
+                widthView = 80
+                iconCat = UIImage(systemName: "music.note")
+                textCat = "Audio".localized()
+                tag = AUDIOS_TAG
+            }
+            let viewCat = UIView(frame: CGRect(x: 0, y: 0, width: widthView, height: 30))
+            groupViewCat.addArrangedSubview(viewCat)
+            viewCat.anchor(width: widthView, height: 30)
+            viewCat.layer.cornerRadius = 15
+            viewCat.layer.borderColor = UIColor.gray.cgColor
+            viewCat.layer.borderWidth = 0.5
+            viewCat.backgroundColor = .white
+            viewCat.isUserInteractionEnabled = true
+            viewCat.tag = tag
+            viewCat.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(onTapCategory(_:))))
+            
+            
+            let imageIcon = UIImageView()
+            imageIcon.image = iconCat
+            imageIcon.tintColor = .black
+            viewCat.addSubview(imageIcon)
+            imageIcon.anchor(left: viewCat.leftAnchor, paddingLeft: 10, centerY: viewCat.centerYAnchor)
+            
+            let imageText = UILabel()
+            imageText.text = textCat
+            viewCat.addSubview(imageText)
+            imageText.anchor(left: imageIcon.rightAnchor, paddingLeft: 5, centerY: viewCat.centerYAnchor)
+            imageText.font = .systemFont(ofSize: 15)
+        }
+        
+        let imageSetting = UIImageView(image: UIImage(named: "nx_setting_sb", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!)
+        imageSetting.contentMode = .scaleToFill
+        imageViewSearch.addSubview(imageSetting)
+        imageSetting.anchor(right: imageViewSearch.rightAnchor, paddingRight: 15, centerY: imageViewSearch.centerYAnchor, width: 20, height: 20)
+        imageSetting.isUserInteractionEnabled = true
+        imageSetting.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(settingTapped)))
+        
+        imageVoiceSb.image = UIImage(named: "nx_mic", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!
+        imageVoiceSb.contentMode = .scaleAspectFit
+        imageViewSearch.addSubview(imageVoiceSb)
+        imageVoiceSb.anchor(right: imageSetting.leftAnchor, paddingRight: 10, centerY: imageViewSearch.centerYAnchor, width: 20, height: 20)
+        imageVoiceSb.isUserInteractionEnabled = true
+        imageVoiceSb.isHidden = true
+        imageVoiceSb.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(recordAudio)))
         
+        imageClearSearch.image = UIImage(systemName: "xmark")
+        imageClearSearch.contentMode = .scaleAspectFit
+        imageClearSearch.tintColor = .gray
+        imageViewSearch.addSubview(imageClearSearch)
+        imageClearSearch.anchor(right: imageVoiceSb.leftAnchor, paddingRight: 5, centerY: imageViewSearch.centerYAnchor, width: 20, height: 20)
+        imageClearSearch.isUserInteractionEnabled = true
+        imageClearSearch.isHidden = true
+        imageClearSearch.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(clearSearch)))
+        
+        textViewSearch.placeholder = "Search...".localized()
+        textViewSearch.isUserInteractionEnabled = true
+        imageViewSearch.addSubview(textViewSearch)
+        textViewSearch.font = .systemFont(ofSize: 11)
+        textViewSearch.anchor(top: imageViewSearch.topAnchor, bottom: imageViewSearch.bottomAnchor, right: imageViewSearch.rightAnchor, paddingTop: 5, paddingBottom: 5, paddingRight: 90)
+        leftTVSearch = textViewSearch.leftAnchor.constraint(equalTo: imageViewSearch.leftAnchor, constant: 20.0)
+        NSLayoutConstraint.activate([
+            leftTVSearch
+        ])
+        textViewSearch.delegate = self
         
         tableView.tableHeaderView = segment
         tableView.tableFooterView = UIView()
@@ -199,6 +380,30 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
         }
     }
     
+    @objc func clearSearch(){
+        selectedTag = 0
+        if gridImage != nil && gridImage.isDescendant(of: self.view) {
+            gridImage.removeFromSuperview()
+            tableView.isHidden = false
+        }
+        textViewSearch.text = ""
+        imageClearSearch.isHidden = true
+        if !textViewSearch.isFirstResponder {
+            viewCategorySearch.isHidden = true
+            imageVoiceSb.isHidden = true
+            heightToolbar.constant = 45
+            filterContentForSearchText("")
+        } else if heightToolbar.constant == 45 {
+            viewCategorySearch.isHidden = false
+            heightToolbar.constant = 85
+        }
+        
+        if leftTVSearch.constant != 20 {
+            viewCatInTV.removeFromSuperview()
+            leftTVSearch.constant = 20
+        }
+    }
+    
     func setupSpeech() {
 
         self.speechRecognizer?.delegate = self
@@ -384,7 +589,7 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
         navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
         navigationController?.navigationBar.shadowImage = UIImage()
         navigationController?.navigationBar.isTranslucent = true
-        navigationController?.setNavigationBarHidden(false, animated: false)
+        navigationController?.setNavigationBarHidden(true, animated: false)
         navigationController?.navigationBar.tintColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
         navigationController?.navigationBar.overrideUserInterfaceStyle = self.traitCollection.userInterfaceStyle == .dark ? .dark : .light
         navigationController?.navigationBar.barStyle = .default
@@ -398,47 +603,7 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
 //            tabBarController?.navigationItem.rightBarButtonItem = menuItem
 //        }
 //        tabBarController?.navigationItem.searchController = searchController
-        let customView = UIView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 30))
-        customView.isUserInteractionEnabled = true
-        
-        let imageViewSearch = UIImageView(image: UIImage(named: self.traitCollection.userInterfaceStyle == .dark ? "nx_search_bar_dark" : "nx_search_bar", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!)
-        imageViewSearch.contentMode = .scaleToFill
-        customView.addSubview(imageViewSearch)
-        imageViewSearch.anchor(top: customView.topAnchor, left: customView.leftAnchor, bottom: customView.bottomAnchor, right: customView.rightAnchor, height: 35)
-        imageViewSearch.isUserInteractionEnabled = true
         
-        let imageSetting = UIImageView(image: UIImage(named: "nx_setting_sb", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!)
-        imageSetting.contentMode = .scaleToFill
-        imageViewSearch.addSubview(imageSetting)
-        imageSetting.anchor(right: customView.rightAnchor, paddingRight: 18, centerY: customView.centerYAnchor, width: 20, height: 20)
-        imageSetting.isUserInteractionEnabled = true
-        imageSetting.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(settingTapped)))
-        
-        imageVoiceSb.image = UIImage(named: "nx_mic", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!
-        imageVoiceSb.contentMode = .scaleAspectFit
-        imageViewSearch.addSubview(imageVoiceSb)
-        imageVoiceSb.anchor(top: imageViewSearch.topAnchor, right: imageSetting.leftAnchor, paddingTop: 5, paddingRight: 15, width: 20, height: 20)
-        imageVoiceSb.isUserInteractionEnabled = true
-        imageVoiceSb.isHidden = true
-        imageVoiceSb.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(recordAudio)))
-        
-        imageNewChatSb.image = UIImage(systemName: "square.and.pencil")!
-        imageNewChatSb.tintColor = .nxColor
-        imageNewChatSb.contentMode = .scaleAspectFit
-        imageViewSearch.addSubview(imageNewChatSb)
-        imageNewChatSb.anchor(top: imageViewSearch.topAnchor, right: imageVoiceSb.leftAnchor, paddingTop: 5, paddingRight: 15, width: 20, height: 20)
-        imageNewChatSb.isUserInteractionEnabled = true
-        imageNewChatSb.isHidden = true
-        imageNewChatSb.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(startConversation)))
-        
-        textViewSearch.placeholder = "Search...".localized()
-        textViewSearch.isUserInteractionEnabled = true
-        imageViewSearch.addSubview(textViewSearch)
-        textViewSearch.font = .systemFont(ofSize: 11)
-        textViewSearch.anchor(top: imageViewSearch.topAnchor, left: imageViewSearch.leftAnchor, bottom: imageViewSearch.bottomAnchor, right: imageViewSearch.rightAnchor, paddingTop: 5, paddingLeft: 20, paddingBottom: 5, paddingRight: 120)
-        textViewSearch.delegate = self
-        
-        tabBarController?.navigationItem.titleView = customView
         let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
         speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: lang ?? "en"))
         backgroundImage.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .black : .white
@@ -496,9 +661,54 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
     
     func textFieldDidChangeSelection(_ textField: UITextField) {
         if textField == textViewSearch {
-            let textSerch = textField.text ?? ""
+            var textSerch = textField.text ?? ""
+            textSerch = textSerch.trimmingCharacters(in: .whitespacesAndNewlines)
+            if !textSerch.isEmpty && imageClearSearch.isHidden {
+                imageClearSearch.isHidden = false
+            } else if textSerch.isEmpty && !imageClearSearch.isHidden {
+                imageClearSearch.isHidden = true
+            }
+            if textField.text?.count == 1 && leftTVSearch.constant != 20 {
+                selectedTag = 0
+                if gridImage != nil && gridImage.isDescendant(of: self.view) {
+                    gridImage.removeFromSuperview()
+                    tableView.isHidden = false
+                }
+                textField.text = ""
+                viewCatInTV.removeFromSuperview()
+                leftTVSearch.constant = 20
+            }
             filterContentForSearchText(textSerch)
         }
+        if viewCatInTV != nil && viewCatInTV.isDescendant(of: imageViewSearch) {
+            viewCategorySearch.isHidden = true
+            heightToolbar.constant = 45
+        } else if (viewCatInTV == nil || !viewCatInTV.isDescendant(of: imageViewSearch)) && segment.selectedSegmentIndex == 0 {
+            viewCategorySearch.isHidden = false
+            heightToolbar.constant = 85
+        }
+    }
+    
+    func textFieldDidBeginEditing(_ textField: UITextField) {
+        if textField == textViewSearch {
+            if segment.selectedSegmentIndex == 0 && (viewCatInTV == nil || !viewCatInTV.isDescendant(of: imageViewSearch)) {
+                heightToolbar.constant = 85
+                viewCategorySearch.isHidden = false
+                imageVoiceSb.isHidden = false
+            }
+        }
+    }
+    
+    func textFieldDidEndEditing(_ textField: UITextField) {
+        if textField == textViewSearch {
+            if let text = textField.text {
+                if text.isEmpty {
+                    viewCategorySearch.isHidden = true
+                    imageVoiceSb.isHidden = true
+                    heightToolbar.constant = 45
+                }
+            }
+        }
     }
     
     override func viewWillDisappear(_ animated: Bool) {
@@ -514,8 +724,77 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
     }
     
     @objc func settingTapped() {
-        imageVoiceSb.isHidden = !imageVoiceSb.isHidden
-        imageNewChatSb.isHidden = !imageNewChatSb.isHidden
+//        imageVoiceSb.isHidden = !imageVoiceSb.isHidden
+//        imageNewChatSb.isHidden = !imageNewChatSb.isHidden
+        startConversation()
+    }
+    
+    @objc func onTapCategory(_ sender: UITapGestureRecognizer) {
+        if let tappedView = sender.view {
+            if viewCatInTV != nil && viewCatInTV.isDescendant(of: imageViewSearch) {
+                viewCatInTV.removeFromSuperview()
+            }
+            var width1: CGFloat = 67
+            var width2: CGFloat = 80
+            viewCatInTV = UIView()
+            imageViewSearch.addSubview(viewCatInTV)
+            
+            var iconCat = UIImage(systemName: "bubble.right")
+            var textCat = "Unread".localized()
+            
+            selectedTag = tappedView.tag
+            
+            if tappedView.tag == PHOTOS_TAG {
+                iconCat = UIImage(systemName: "photo")
+                textCat = "Photos".localized()
+            } else if tappedView.tag == DOCUMENTS_TAG {
+                iconCat = UIImage(systemName: "doc")
+                textCat = "Documents".localized()
+                width1 = 97
+                width2 = 110
+            } else if tappedView.tag == LINKS_TAG {
+                iconCat = UIImage(systemName: "link")
+                textCat = "Links".localized()
+                width1 = 57
+                width2 = 70
+            } else if tappedView.tag == VIDEOS_TAG {
+                iconCat = UIImage(systemName: "video")
+                textCat = "Videos".localized()
+            } else if tappedView.tag == GIFS_TAG {
+                iconCat = UIImage(systemName: "photo.on.rectangle")
+                textCat = "GIFs".localized()
+                width1 = 52
+                width2 = 65
+            } else if tappedView.tag == AUDIOS_TAG {
+                iconCat = UIImage(systemName: "music.note")
+                textCat = "Audio".localized()
+                width1 = 62
+                width2 = 75
+            }
+            
+            viewCatInTV.frame = CGRect(x: 0, y: 0, width: width1, height: 30)
+            viewCatInTV.anchor(left: imageViewSearch.leftAnchor, paddingLeft: 20, centerY: imageViewSearch.centerYAnchor, width: width1, height: 25)
+            viewCatInTV.backgroundColor = .white
+            viewCatInTV.layer.cornerRadius = 8
+            viewCatInTV.clipsToBounds = true
+            viewCatInTV.backgroundColor = .black
+            
+            textViewSearch.text = "~ "
+            leftTVSearch.constant = width2
+            
+            let imageIcon = UIImageView()
+            imageIcon.image = iconCat
+            imageIcon.tintColor = .white
+            viewCatInTV.addSubview(imageIcon)
+            imageIcon.anchor(left: viewCatInTV.leftAnchor, paddingLeft: 5, centerY: viewCatInTV.centerYAnchor, width: 12, height: 12)
+            
+            let imageText = UILabel()
+            imageText.text = textCat
+            viewCatInTV.addSubview(imageText)
+            imageText.textColor = .white
+            imageText.anchor(left: imageIcon.rightAnchor, paddingLeft: 3, centerY: viewCatInTV.centerYAnchor, height: 12)
+            imageText.font = .systemFont(ofSize: 12)
+        }
     }
     
     @objc func onReloadTab(notification: NSNotification) {
@@ -544,8 +823,9 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
                 let chatData = dataMessage.mBodies
                 if chatData[CoreMessage_TMessageKey.IS_CALL_CENTER] == nil || chatData[CoreMessage_TMessageKey.IS_CALL_CENTER] == "0" {
                     var indexChat: Int?
-                    if chatData[CoreMessage_TMessageKey.MESSAGE_SCOPE_ID] == "3" && chatData[CoreMessage_TMessageKey.F_PIN] != User.getMyPin() {
-                        indexChat = chats.firstIndex(where: { $0.fpin == chatData[CoreMessage_TMessageKey.F_PIN] })
+                    if chatData[CoreMessage_TMessageKey.MESSAGE_SCOPE_ID] == "3" {
+                        let f_pin = chatData[CoreMessage_TMessageKey.F_PIN] != User.getMyPin() ? chatData[CoreMessage_TMessageKey.F_PIN] : chatData[CoreMessage_TMessageKey.L_PIN]
+                        indexChat = chats.firstIndex(where: { $0.pin == f_pin })
                     } else if chatData[CoreMessage_TMessageKey.MESSAGE_SCOPE_ID] == "4" && chatData[CoreMessage_TMessageKey.F_PIN] != User.getMyPin() {
                         indexChat = chats.firstIndex(where: { (chatData[CoreMessage_TMessageKey.CHAT_ID] ?? "").isEmpty ? $0.pin == chatData[CoreMessage_TMessageKey.L_PIN] : $0.pin == chatData[CoreMessage_TMessageKey.CHAT_ID] })
                     }
@@ -1130,6 +1410,294 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
                 }
                 data = chats[indexPath.row]
             }
+            if selectedTag != UNREAD_TAG && selectedTag != 0 {
+                let title = UILabel()
+                let subtitle = UILabel()
+                title.textColor = .black
+                subtitle.textColor = .gray
+                title.font = .systemFont(ofSize: 16, weight: .medium)
+                subtitle.font = .systemFont(ofSize: 14)
+                content.addSubview(title)
+                content.addSubview(subtitle)
+                title.anchor(top: content.topAnchor, left: content.leftAnchor, paddingTop: 10, paddingLeft: 20)
+                subtitle.anchor(top: title.bottomAnchor, left: content.leftAnchor, right: content.rightAnchor, paddingLeft: 20, paddingRight: 20)
+                subtitle.numberOfLines = 2
+                title.text = data.name
+                
+                let imageArrowRight = UIImageView(image: UIImage(systemName: "chevron.right"))
+                content.addSubview(imageArrowRight)
+                imageArrowRight.tintColor = .gray
+                imageArrowRight.anchor(top: content.topAnchor, right: content.rightAnchor, paddingTop: 10, paddingRight: 20)
+                
+                let timeView = UILabel()
+                content.addSubview(timeView)
+                timeView.anchor(top: content.topAnchor, right: imageArrowRight.leftAnchor, paddingTop: 10, paddingRight: 20)
+                timeView.textColor = .gray
+                timeView.font = UIFont.systemFont(ofSize: 16)
+                
+                let date = Date(milliseconds: Int64(data.serverDate)!)
+                let calendar = Calendar.current
+                
+                if (calendar.isDateInToday(date)) {
+                    let formatter = DateFormatter()
+                    formatter.dateFormat = "HH:mm"
+                    formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
+                    timeView.text = formatter.string(from: date as Date)
+                } else {
+                    let startOfNow = calendar.startOfDay(for: Date())
+                    let startOfTimeStamp = calendar.startOfDay(for: date)
+                    let components = calendar.dateComponents([.day], from: startOfNow, to: startOfTimeStamp)
+                    let day = -(components.day!)
+                    if day == 1 {
+                        timeView.text = "Yesterday".localized()
+                    } else {
+                        if day < 7 {
+                            let formatter = DateFormatter()
+                            formatter.dateFormat = "EEEE"
+                            let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
+                            if lang == "id" {
+                                formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
+                            }
+                            timeView.text = formatter.string(from: date)
+                        } else {
+                            let formatter = DateFormatter()
+                            formatter.dateFormat = "M/dd/yy"
+                            formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
+                            let stringFormat = formatter.string(from: date as Date)
+                            timeView.text = stringFormat
+                        }
+                    }
+                }
+                
+                cell.separatorInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 0)
+                
+                let container = UIView()
+                content.addSubview(container)
+                container.anchor(top: subtitle.bottomAnchor, left: content.leftAnchor, right: content.rightAnchor, paddingTop: 5, paddingLeft: 20, paddingRight: 20, height: selectedTag == LINKS_TAG ? 75 : 60)
+                container.backgroundColor = .lightGray.withAlphaComponent(0.3)
+                container.layer.cornerRadius = 15
+                container.clipsToBounds = true
+                container.isUserInteractionEnabled = true
+                
+                if selectedTag == DOCUMENTS_TAG {
+                    subtitle.text = "📄 " + "Document".localized()
+                    
+                    let imageFile = UIImageView(image: UIImage(systemName: "doc.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 45)))
+                    container.addSubview(imageFile)
+                    imageFile.tintColor = .black
+                    imageFile.anchor(top: container.topAnchor, left: container.leftAnchor, bottom: container.bottomAnchor, paddingTop: 5, paddingLeft: 5, paddingBottom: 5, width: 45)
+                    
+                    let nameFile = UILabel()
+                    container.addSubview(nameFile)
+                    nameFile.font = .systemFont(ofSize: 12, weight: .medium)
+                    nameFile.textColor = .black
+                    nameFile.numberOfLines = 2
+                    nameFile.anchor(top: container.topAnchor, left: imageFile.rightAnchor, right: container.rightAnchor, paddingTop: 5, paddingLeft: 10, paddingRight: 5)
+                    nameFile.text = data.messageText.components(separatedBy: "|")[0]
+                    
+                    let fileSub = UILabel()
+                    let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
+                    let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
+                    let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
+                    let arrExtFile = (data.messageText.components(separatedBy: "|")[0]).split(separator: ".")
+                    let finalExtFile = arrExtFile[arrExtFile.count - 1]
+                    if let dirPath = paths.first {
+                        let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(data.file)
+                        if FileManager.default.fileExists(atPath: fileURL.path) {
+                            if let dataFile = try? Data(contentsOf: fileURL) {
+                                var sizeOfFile = Int(dataFile.count / 1000000)
+                                if (sizeOfFile < 1) {
+                                    sizeOfFile = Int(dataFile.count / 1000)
+                                    if (finalExtFile.count > 4) {
+                                        fileSub.text = "\(sizeOfFile) kB \u{2022} TXT"
+                                    }else {
+                                        fileSub.text = "\(sizeOfFile) kB \u{2022} \(finalExtFile.uppercased())"
+                                    }
+                                } else {
+                                    if (finalExtFile.count > 4) {
+                                        fileSub.text = "\(sizeOfFile) MB \u{2022} TXT"
+                                    }else {
+                                        fileSub.text = "\(sizeOfFile) MB \u{2022} \(finalExtFile.uppercased())"
+                                    }
+                                }
+                            } else {
+                                fileSub.text = ""
+                            }
+                        }
+                        else if FileEncryption.shared.isSecureExists(filename: data.file) {
+                            if let dataFile = try? FileEncryption.shared.readSecure(filename: data.file) {
+                                var sizeOfFile = Int(dataFile.count / 1000000)
+                                if (sizeOfFile < 1) {
+                                    sizeOfFile = Int(dataFile.count / 1000)
+                                    if (finalExtFile.count > 4) {
+                                        fileSub.text = "\(sizeOfFile) kB \u{2022} TXT"
+                                    }else {
+                                        fileSub.text = "\(sizeOfFile) kB \u{2022} \(finalExtFile.uppercased())"
+                                    }
+                                } else {
+                                    if (finalExtFile.count > 4) {
+                                        fileSub.text = "\(sizeOfFile) MB \u{2022} TXT"
+                                    }else {
+                                        fileSub.text = "\(sizeOfFile) MB \u{2022} \(finalExtFile.uppercased())"
+                                    }
+                                }
+                            } else {
+                                fileSub.text = ""
+                            }
+                        }
+                        container.addSubview(fileSub)
+                        fileSub.anchor(top: nameFile.bottomAnchor, left: imageFile.rightAnchor, bottom: container.bottomAnchor, paddingLeft: 10, paddingBottom: 5)
+                        fileSub.font = .systemFont(ofSize: 10)
+                        fileSub.textColor = .gray
+                        let objectTap = ObjectGesture(target: self, action: #selector(onContSearch(_:)))
+                        objectTap.file_id = data.file
+                        container.addGestureRecognizer(objectTap)
+                    }
+                } else if selectedTag == LINKS_TAG {
+                    var text = ""
+                    var txtData = data.messageText
+                    if txtData.contains("■"){
+                        txtData = txtData.components(separatedBy: "■")[0]
+                        txtData = txtData.trimmingCharacters(in: .whitespacesAndNewlines)
+                    }
+                    let listTextSplitBreak = txtData.components(separatedBy: "\n")
+                    let indexFirstLinkSplitBreak = listTextSplitBreak.firstIndex(where: { $0.contains("www.") || $0.contains("http://") || $0.contains("https://") })
+                    if indexFirstLinkSplitBreak != nil {
+                        let listTextSplitSpace = listTextSplitBreak[indexFirstLinkSplitBreak!].components(separatedBy: " ")
+                        let indexFirstLinkSplitSpace = listTextSplitSpace.firstIndex(where: { ($0.starts(with: "www.") && $0.components(separatedBy: ".").count > 2) || ($0.starts(with: "http://") && $0.components(separatedBy: ".").count > 1) || ($0.starts(with: "https://") && $0.components(separatedBy: ".").count > 1) })
+                        if indexFirstLinkSplitSpace != nil {
+                            text = listTextSplitSpace[indexFirstLinkSplitSpace!]
+                        }
+                    }
+                    var dataURL = ""
+                    subtitle.text = txtData
+                    Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                        do {
+                            if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select data_link from LINK_PREVIEW where link='\(text)'"), cursor.next() {
+                                if let data = cursor.string(forColumnIndex: 0) {
+                                    dataURL = data
+                                }
+                                cursor.close()
+                            }
+                        } catch {
+                            rollback.pointee = true
+                            print("Access database error: \(error.localizedDescription)")
+                        }
+                    })
+                    
+                    var title = ""
+                    var description = ""
+                    var imageUrl: String?
+                    var link = text
+                    
+                    let objectTap = ObjectGesture(target: self, action: #selector(onContSearch(_:)))
+                    objectTap.message_id = link
+                    container.addGestureRecognizer(objectTap)
+                    
+                    let imagePreview = UIImageView()
+                    container.addSubview(imagePreview)
+                    imagePreview.translatesAutoresizingMaskIntoConstraints = false
+                    imagePreview.leadingAnchor.constraint(equalTo: container.leadingAnchor).isActive = true
+                    imagePreview.bottomAnchor.constraint(equalTo: container.bottomAnchor).isActive = true
+                    imagePreview.topAnchor.constraint(equalTo: container.topAnchor).isActive = true
+                    imagePreview.widthAnchor.constraint(equalToConstant: 80.0).isActive = true
+                    
+                    imagePreview.image = UIImage(systemName: "link", withConfiguration: UIImage.SymbolConfiguration(pointSize: 45))
+                    imagePreview.contentMode = .center
+                    imagePreview.clipsToBounds = true
+                    imagePreview.tintColor = .black
+                    imagePreview.backgroundColor = .gray.withAlphaComponent(0.3)
+                    
+                    let titlePreview = UILabel()
+                    container.addSubview(titlePreview)
+                    titlePreview.translatesAutoresizingMaskIntoConstraints = false
+                    titlePreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
+                    titlePreview.topAnchor.constraint(equalTo: container.topAnchor, constant: 10.0).isActive = true
+                    titlePreview.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -5.0).isActive = true
+                    titlePreview.text = title
+                    titlePreview.font = UIFont.systemFont(ofSize: 14.0, weight: .bold)
+                    titlePreview.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
+                    
+                    let descPreview = UILabel()
+                    container.addSubview(descPreview)
+                    descPreview.translatesAutoresizingMaskIntoConstraints = false
+                    descPreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
+                    descPreview.topAnchor.constraint(equalTo: titlePreview.bottomAnchor).isActive = true
+                    descPreview.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -5.0).isActive = true
+                    descPreview.text = description
+                    descPreview.font = UIFont.systemFont(ofSize: 12.0)
+                    descPreview.textColor = .gray
+                    descPreview.numberOfLines = 1
+                    
+                    let linkPreview = UILabel()
+                    container.addSubview(linkPreview)
+                    linkPreview.translatesAutoresizingMaskIntoConstraints = false
+                    linkPreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
+                    linkPreview.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -5.0).isActive = true
+                    linkPreview.font = UIFont.systemFont(ofSize: 10.0)
+                    linkPreview.textColor = .gray
+                    linkPreview.numberOfLines = 1
+                    
+                    if !dataURL.isEmpty {
+                        if let data = try! JSONSerialization.jsonObject(with: dataURL.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
+                            title = data["title"] as! String
+                            description = data["description"] as! String
+                            imageUrl = data["imageUrl"] as? String
+                            link = data["link"] as! String
+                            
+                            if imageUrl != nil {
+                                imagePreview.loadImageAsync(with: imageUrl)
+                                imagePreview.contentMode = .scaleToFill
+                                imagePreview.clipsToBounds = true
+                            }
+                            
+                            titlePreview.text = title
+                            descPreview.text = description
+                            linkPreview.text = link
+                            linkPreview.topAnchor.constraint(equalTo: descPreview.bottomAnchor, constant: 8.0).isActive = true
+                        }
+                    } else {
+                        linkPreview.text = link
+                        linkPreview.centerYAnchor.constraint(equalTo: container.centerYAnchor).isActive = true
+                    }
+                } else if selectedTag == AUDIOS_TAG {
+                    subtitle.text = "♫ " + "Audio".localized()
+                    
+                    let imageAudio = UIImageView()
+                    imageAudio.image = UIImage(systemName: "music.note", withConfiguration: UIImage.SymbolConfiguration(pointSize: 35))
+                    container.addSubview(imageAudio)
+                    imageAudio.anchor(left: container.leftAnchor, paddingLeft: 10, centerY: container.centerYAnchor)
+                    imageAudio.tintColor = .black
+                    
+                    let nameAudio = UILabel()
+                    container.addSubview(nameAudio)
+                    nameAudio.anchor(left: imageAudio.rightAnchor, right: container.rightAnchor, paddingLeft: 10, paddingRight: 10, centerY: container.centerYAnchor)
+                    nameAudio.numberOfLines = 2
+                    nameAudio.text = data.messageText.components(separatedBy: "|")[0]
+                    nameAudio.font = .systemFont(ofSize: 16, weight: .medium)
+                    
+                    let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
+                    let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
+                    let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
+                    if let dirPath = paths.first {
+                        let audioURL = URL(fileURLWithPath: dirPath).appendingPathComponent(data.audio)
+                        if !FileManager.default.fileExists(atPath: audioURL.path) && !FileEncryption.shared.isSecureExists(filename: data.audio) {
+                            Download().startHTTP(forKey: data.audio) { (name, progress) in
+                                guard progress == 100 else {
+                                    return
+                                }
+                                tableView.reloadRows(at: [indexPath], with: .none)
+                            }
+                        } else {
+                            let objectTap = ObjectGesture(target: self, action: #selector(onContSearch(_:)))
+                            objectTap.audio_id = data.audio
+                            container.addGestureRecognizer(objectTap)
+                        }
+                    }
+                }
+                return cell
+            }
+            
             let imageView = UIImageView()
             content.addSubview(imageView)
             imageView.translatesAutoresizingMaskIntoConstraints = false
@@ -1378,7 +1946,105 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
         return cell
     }
     
+    @objc func onContSearch(_ sender: ObjectGesture) {
+        if selectedTag == PHOTOS_TAG {
+            
+        } else if selectedTag == VIDEOS_TAG {
+            
+        } else if selectedTag == DOCUMENTS_TAG {
+            let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
+            let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
+            let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
+            if let dirPath = paths.first {
+                let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.file_id)
+                if FileManager.default.fileExists(atPath: fileURL.path) {
+                    self.previewItem = fileURL as NSURL
+                    let previewController = QLPreviewController()
+                    let rightBarButton = UIBarButtonItem()
+                    previewController.navigationItem.rightBarButtonItem = rightBarButton
+                    previewController.dataSource = self
+                    previewController.modalPresentationStyle = .custom
+                    
+                    self.present(previewController, animated: true)
+                } else if FileEncryption.shared.isSecureExists(filename: sender.file_id) {
+                    do {
+                        if let docData = try FileEncryption.shared.readSecure(filename: sender.file_id) {
+                            
+                            let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
+                            let tempPath = cachesDirectory.appendingPathComponent(sender.file_id)
+                            try docData.write(to: tempPath)
+                            self.previewItem = tempPath as NSURL
+                            let previewController = QLPreviewController()
+                            let rightBarButton = UIBarButtonItem()
+                            previewController.navigationItem.rightBarButtonItem = rightBarButton
+                            previewController.dataSource = self
+                            previewController.modalPresentationStyle = .custom
+                            self.present(previewController,animated: true)
+                        }
+                    }
+                    catch {
+                        
+                    }
+                }
+            }
+        } else if selectedTag == LINKS_TAG {
+            var stringURl = sender.message_id
+            if stringURl.lowercased().starts(with: "www.") {
+                stringURl = "https://" + stringURl.replacingOccurrences(of: "www.", with: "")
+            }
+            guard let url = URL(string: stringURl) else { return }
+            UIApplication.shared.open(url)
+        } else if selectedTag == AUDIOS_TAG {
+            let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
+            let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
+            let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
+            if let dirPath = paths.first {
+                let audioURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.audio_id)
+                if FileManager.default.fileExists(atPath: audioURL.path) {
+                    do {
+                        if audioPlayer == nil || audioPlayer?.url != audioURL {
+                            audioPlayer = try AVAudioPlayer(contentsOf: audioURL)
+                            audioPlayer?.prepareToPlay()
+                            audioPlayer?.play()
+                        } else if audioPlayer!.isPlaying {
+                            audioPlayer?.pause()
+                        } else {
+                            audioPlayer?.play()
+                        }
+                    } catch {
+                        
+                    }
+                } else if FileEncryption.shared.isSecureExists(filename: sender.audio_id) {
+                    do {
+                        if let audioData = try FileEncryption.shared.readSecure(filename: sender.audio_id) {
+                            let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
+                            let tempPath = cachesDirectory.appendingPathComponent(sender.audio_id)
+                            try audioData.write(to: tempPath)
+                            if audioPlayer == nil || audioPlayer?.url != tempPath {
+                                audioPlayer = try AVAudioPlayer(contentsOf: tempPath)
+                                audioPlayer?.prepareToPlay()
+                                audioPlayer?.play()
+                            } else if audioPlayer!.isPlaying {
+                                audioPlayer?.pause()
+                            } else {
+                                audioPlayer?.play()
+                            }
+                        }
+                    } catch {
+                        
+                    }
+                }
+            }
+        }
+    }
+    
     func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
+        if fillteredData.count > 0 && selectedTag != UNREAD_TAG && selectedTag != 0 {
+            if selectedTag == LINKS_TAG {
+                return 160.0
+            }
+            return 130.0
+        }
         return 75.0
     }
     
@@ -1397,6 +2063,141 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
         return status
     }
     
+    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+        return fillteredData.count
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "gridCell", for: indexPath)
+        if cell.contentView.subviews.count > 0 {
+            cell.contentView.subviews.forEach { $0.removeFromSuperview() }
+        }
+        let data = fillteredData[indexPath.row] as! Chat
+        let thumb = data.thumb
+        let imgData = data.image
+        let vidData = data.video
+        let image = UIImageView()
+        cell.contentView.addSubview(image)
+        let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
+        let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
+        let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
+        if let dirPath = paths.first {
+            let thumbURL = URL(fileURLWithPath: dirPath).appendingPathComponent(thumb)
+            let imageT : UIImage? =  {
+                if let img = Nexilis.imageCache.object(forKey: thumb as NSString) {
+                    return img
+                }
+                else if let img = UIImage(contentsOfFile: thumbURL.path)?.resize(target: CGSize(width: 500, height: 500)) {
+                    Nexilis.imageCache.setObject(img, forKey: thumb as NSString)
+                    return img
+                }
+                return nil
+            }()
+            if imageT != nil {
+                image.image = imageT
+            }
+            
+            let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(imgData)
+            let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(vidData)
+            if (!FileManager.default.fileExists(atPath: imageURL.path) && !FileEncryption.shared.isSecureExists(filename: imageURL.lastPathComponent)) || (!FileManager.default.fileExists(atPath: videoURL.path) && !FileEncryption.shared.isSecureExists(filename: videoURL.lastPathComponent)) {
+                let blurEffect = UIBlurEffect(style: UIBlurEffect.Style.light)
+                let blurEffectView = UIVisualEffectView(effect: blurEffect)
+                blurEffectView.frame = CGRect(x: 0, y: 0, width: image.frame.size.width, height: image.frame.size.height)
+                blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+                image.addSubview(blurEffectView)
+                if !imgData.isEmpty {
+                    let imageDownload = UIImageView(image: UIImage(systemName: "arrow.down.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 50, weight: .bold, scale: .default)))
+                    image.addSubview(imageDownload)
+                    imageDownload.tintColor = .black.withAlphaComponent(0.3)
+                    imageDownload.translatesAutoresizingMaskIntoConstraints = false
+                    imageDownload.centerXAnchor.constraint(equalTo: image.centerXAnchor).isActive = true
+                    imageDownload.centerYAnchor.constraint(equalTo: image.centerYAnchor).isActive = true
+                }
+            }
+            if (!vidData.isEmpty) {
+                let imagePlay = UIImageView(image: UIImage(systemName: "play.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .bold, scale: .default))?.imageWithInsets(insets: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10))?.withTintColor(.white))
+                imagePlay.circle()
+                image.addSubview(imagePlay)
+                imagePlay.backgroundColor = .black.withAlphaComponent(0.3)
+                imagePlay.translatesAutoresizingMaskIntoConstraints = false
+                imagePlay.centerXAnchor.constraint(equalTo: image.centerXAnchor).isActive = true
+                imagePlay.centerYAnchor.constraint(equalTo: image.centerYAnchor).isActive = true
+            }
+        }
+        image.contentMode = .scaleAspectFill
+        image.clipsToBounds = true
+        image.anchor(top: cell.contentView.topAnchor, left: cell.contentView.leftAnchor, bottom: cell.contentView.bottomAnchor, right: cell.contentView.rightAnchor)
+        return cell
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+        let data = fillteredData[indexPath.row] as! Chat
+        let imgData = data.image
+        let vidData = data.video
+        let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
+        let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
+        let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
+        if let dirPath = paths.first {
+            if selectedTag == PHOTOS_TAG {
+                let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(imgData)
+                if FileManager.default.fileExists(atPath: imageURL.path) {
+                    let image = UIImage(contentsOfFile: imageURL.path) ?? UIImage()
+                    APIS.openImageNexilis(image: image)
+                } else if FileEncryption.shared.isSecureExists(filename: imgData) {
+                    do {
+                        let data = try FileEncryption.shared.readSecure(filename: imgData)
+                        let image = UIImage(data: data!) ?? UIImage()
+                        APIS.openImageNexilis(image: image)
+                    }
+                    catch {
+                    }
+                } else {
+                    Download().startHTTP(forKey: imgData) { (name, progress) in
+                        guard progress == 100 else {
+                            return
+                        }
+                        DispatchQueue.main.async {
+                            collectionView.reloadItems(at: [indexPath])
+                        }
+                    }
+                }
+            } else {
+                let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(vidData)
+                if FileManager.default.fileExists(atPath: videoURL.path) {
+                    APIS.openVideoNexilis(videoURL: videoURL)
+                } else if FileEncryption.shared.isSecureExists(filename: vidData) {
+                    do {
+                        if let secureData = try FileEncryption.shared.readSecure(filename: vidData) {
+                            let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
+                            let tempPath = cachesDirectory.appendingPathComponent(vidData)
+                            try secureData.write(to: tempPath)
+                            APIS.openVideoNexilis(videoURL: tempPath)
+                        }
+                    } catch {
+                        
+                    }
+                } else {
+                    Download().startHTTP(forKey: vidData) { (name, progress) in
+                        guard progress == 100 else {
+                            return
+                        }
+                        DispatchQueue.main.async {
+                            collectionView.reloadItems(at: [indexPath])
+                        }
+                    }
+                }
+            }
+        }
+    }
+    
+    func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
+        return self.previewItem != nil ? 1 : 0
+    }
+    
+    func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> any QLPreviewItem {
+        return self.previewItem!
+    }
+    
 }
 
 

+ 117 - 2
AppBuilder/AppBuilder/ThirdTabViewController.swift

@@ -11,7 +11,7 @@ import NexilisLite
 import Speech
 import CommonCrypto
 
-class ThirdTabViewController: UIViewController, UIScrollViewDelegate, UIGestureRecognizerDelegate, WKNavigationDelegate, WKScriptMessageHandler  {
+class ThirdTabViewController: UIViewController, UIScrollViewDelegate, UIGestureRecognizerDelegate, WKNavigationDelegate, WKScriptMessageHandler, ImageVideoPickerDelegate  {
 
     var webView: WKWebView!
     var address = ""
@@ -32,6 +32,9 @@ class ThirdTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
     public static var atFirstPage = true
     public static var showModal = false
     
+    var indexImageVideoWv = 0
+    var imageVideoPicker: ImageVideoPicker!
+    
     override func viewDidLoad() {
         super.viewDidLoad()
         
@@ -71,6 +74,7 @@ class ThirdTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
         contentController.add(self, name: "closeProfile")
         contentController.add(self, name: "tabShowHide")
         contentController.add(self, name: "shareText")
+        contentController.add(self, name: "openGalleryiOS")
         
         let source: String = "var meta = document.createElement('meta');" +
             "meta.name = 'viewport';" +
@@ -92,6 +96,8 @@ class ThirdTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
         
         NotificationCenter.default.addObserver(self, selector: #selector(onShowAC(notification:)), name: NSNotification.Name(rawValue: "onShowAC"), object: nil)
         NotificationCenter.default.addObserver(self, selector: #selector(onRefreshWebView(notification:)), name: NSNotification.Name(rawValue: "onRefreshWebView"), object: nil)
+        
+        imageVideoPicker = ImageVideoPicker(presentationController: self, delegate: self)
     }
     
     func loadURLWithCookie(url: URL) {
@@ -195,6 +201,7 @@ class ThirdTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
                             viewController.tabBar.isHidden = false
                             ViewController.middleButton.isHidden = false
                             ViewController.alwaysHideButton = false
+                            viewController.indicatorImage.isHidden = false
                         }
                     }
                 } else if PrefsUtil.getCpaasMode() != PrefsUtil.CPAAS_MODE_DOCKED {
@@ -203,6 +210,7 @@ class ThirdTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
                             if viewController.tabBar.isHidden {
                                 viewController.tabBar.isHidden = false
                                 ViewController.alwaysHideButton = false
+                                viewController.indicatorImage.isHidden = false
                             }
                         }
                     }
@@ -268,6 +276,7 @@ class ThirdTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
                 ViewController.hideDockedButton()
                 if let viewController = viewController as? ViewController {
                     viewController.tabBar.isHidden = true
+                    viewController.indicatorImage.isHidden = true
                 }
                 ViewController.removeMiddleButton()
             }
@@ -276,6 +285,7 @@ class ThirdTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
                 if let viewController = viewController as? ViewController {
                     if !viewController.tabBar.isHidden {
                         viewController.tabBar.isHidden = true
+                        viewController.indicatorImage.isHidden = true
                     }
                 }
             }
@@ -294,12 +304,14 @@ class ThirdTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
             if let viewController = viewController as? ViewController {
                 viewController.tabBar.isHidden = false
                 ViewController.middleButton.isHidden = false
+                viewController.indicatorImage.isHidden = false
             }
         } else if PrefsUtil.getCpaasMode() != PrefsUtil.CPAAS_MODE_DOCKED {
             DispatchQueue.main.async {
                 if let viewController = viewController as? ViewController {
                     if viewController.tabBar.isHidden {
                         viewController.tabBar.isHidden = false
+                        viewController.indicatorImage.isHidden = false
                     }
                 }
             }
@@ -449,13 +461,116 @@ class ThirdTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
                 }
             }
         } else if message.name == "shareText" {
-            print("HMM masuk share text")
             guard let dict = message.body as? [String: AnyObject],
                   let param1 = dict["param1"] as? String else {
                 return
             }
             let activityViewController = UIActivityViewController(activityItems: [param1], applicationActivities: nil)
             self.present(activityViewController, animated: true, completion: nil)
+        } else if message.name == "openGalleryiOS" {
+            guard let dict = message.body as? [String: AnyObject],
+                  let param1 = dict["param1"] as? Int else {
+                return
+            }
+            indexImageVideoWv = param1
+            let alertController = LibAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
+            
+            if let action = self.actionImageVideo(for: "image", title: "Choose Photo".localized()) {
+                alertController.addAction(action)
+            }
+            if let action = self.actionImageVideo(for: "video", title: "Choose Video".localized()) {
+                alertController.addAction(action)
+            }
+            alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: nil))
+            self.present(alertController, animated: true)
+        }
+    }
+    
+    private func actionImageVideo(for type: String, title: String) -> UIAlertAction? {
+        return UIAlertAction(title: title, style: .default) { [unowned self] _ in
+            switch type {
+            case "image":
+                imageVideoPicker.present(source: .imageAlbum)
+            case "video":
+                imageVideoPicker.present(source: .videoAlbum)
+            default:
+                imageVideoPicker.present(source: .imageAlbum)
+            }
+        }
+    }
+    
+    func didSelect(imagevideo: Any?) {
+        if imagevideo != nil {
+            let imageData = imagevideo! as! [UIImagePickerController.InfoKey : Any]
+            if (imageData[.mediaType] as! String == "public.image") {
+                let compressedImage = (imageData[.originalImage] as! UIImage).pngData()!
+                let base64String = compressedImage.base64EncodedString()
+                let base64ToWeb = "data:image/jpeg;base64,\(base64String)"
+                webView.evaluateJavaScript("loadFromMobile('\(base64ToWeb)',\(indexImageVideoWv))") { (result, error) in
+                    if let error = error {
+                        print("Error executing JavaScript: \(error)")
+                    }
+                }
+            } else {
+                guard var dataVideo = try? Data(contentsOf: imageData[.mediaURL] as! URL) else {
+                    return
+                }
+                let sizeOfVideo = Double(dataVideo.count / 1048576)
+                if (sizeOfVideo > 10.0) {
+                    let compressedURL = NSURL.fileURL(withPath: NSTemporaryDirectory() + UUID().uuidString + ".mp4")
+                    compressVideo(inputURL: imageData[.mediaURL] as! URL,
+                                  outputURL: compressedURL) { exportSession in
+                        guard let session = exportSession else {
+                            return
+                        }
+                        
+                        switch session.status {
+                        case .unknown:
+                            break
+                        case .waiting:
+                            break
+                        case .exporting:
+                            break
+                        case .completed:
+                            guard let compressedData = try? Data(contentsOf: compressedURL) else {
+                                return
+                            }
+                            dataVideo = compressedData
+                        case .failed:
+                            break
+                        case .cancelled:
+                            break
+                        @unknown default:
+                            break
+                        }
+                    }
+                }
+                let base64String = dataVideo.base64EncodedString()
+                let base64ToWeb = "data:video/mp4;base64,\(base64String)"
+                webView.evaluateJavaScript("loadFromMobile('\(base64ToWeb)',\(indexImageVideoWv))") { (result, error) in
+                    if let error = error {
+                        print("Error executing JavaScript: \(error)")
+                    }
+                }
+            }
+        }
+    }
+    
+    func compressVideo(inputURL: URL,
+                       outputURL: URL,
+                       handler:@escaping (_ exportSession: AVAssetExportSession?) -> Void) {
+        let urlAsset = AVURLAsset(url: inputURL, options: nil)
+        guard let exportSession = AVAssetExportSession(asset: urlAsset,
+                                                       presetName: AVAssetExportPresetMediumQuality) else {
+            handler(nil)
+            
+            return
+        }
+        
+        exportSession.outputURL = outputURL
+        exportSession.outputFileType = .mp4
+        exportSession.exportAsynchronously {
+            handler(exportSession)
         }
     }
     

+ 1 - 1
AppBuilder/AppBuilder/ViewController.swift

@@ -62,7 +62,7 @@ class ViewController: UITabBarController, UITabBarControllerDelegate, SettingMAB
     override func viewDidLoad() {
         super.viewDidLoad()
         DispatchQueue.main.async { [self] in
-            while !Utils.getFinishInitPrefsr() || HTTPCookieStorage.shared.cookies?.count == 0 {
+            while !Utils.getFinishInitPrefsr() || HTTPCookieStorage.shared.cookies(for: URL(string: Utils.getDomainOpr())!)!.count == 0 {
                
             }
             startView()

+ 6 - 0
NexilisLite/NexilisLite/Resource/id.lproj/Localizable.strings

@@ -377,4 +377,10 @@
 "Failed, blocked user" = "Gagal, pengguna telah diblokir";
 "Feature disabled.." = "Fitur dinonaktifkan..";
 "Message has expired" = "Pesan telah kadaluarsa";
+"More..." = "Lainnya...";
+"Translate" = "Terjemahkan";
+"Get Chat Suggestion" = "Dapatkan Saran Obrolan";
+"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.";
 

+ 27 - 0
NexilisLite/NexilisLite/Source/APIS.swift

@@ -11,6 +11,8 @@ import FMDB
 import NotificationBannerSwift
 import Toast_Swift
 import nuSDKService
+import AVFoundation
+import AVKit
 
 public class APIS: NSObject {
     public static func connect(appName: String, apiKey: String, delegate: ConnectDelegate, showButton: Bool = true, fromMAB: Bool = false) {
@@ -812,6 +814,31 @@ public class APIS: NSObject {
 //        Utils.bPreventScreenCapture = isActive
     }
     
+    public static func openImageNexilis(image: UIImage) {
+        let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
+        previewImageVC.image = image
+        previewImageVC.isHiddenTextField = true
+        previewImageVC.modalPresentationStyle = .custom
+        previewImageVC.modalTransitionStyle  = .crossDissolve
+        if UIApplication.shared.visibleViewController?.navigationController != nil {
+            UIApplication.shared.visibleViewController?.navigationController?.present(previewImageVC, animated: true, completion: nil)
+        } else {
+            UIApplication.shared.visibleViewController?.present(previewImageVC, animated: true, completion: nil)
+        }
+    }
+    
+    public static func openVideoNexilis(videoURL: URL) {
+        let player = AVPlayer(url: videoURL)
+        let playerVC = AVPlayerViewController()
+        playerVC.modalPresentationStyle = .custom
+        playerVC.player = player
+        if UIApplication.shared.visibleViewController?.navigationController != nil {
+            UIApplication.shared.visibleViewController?.navigationController?.present(playerVC, animated: true, completion: nil)
+        } else {
+            UIApplication.shared.visibleViewController?.present(playerVC, animated: true, completion: nil)
+        }
+    }
+    
     private static var appNm = "";
     public static func getAppNm() -> String {
         return appNm

+ 48 - 2
NexilisLite/NexilisLite/Source/Extension.swift

@@ -276,7 +276,7 @@ extension UIImage {
         return destinationData as Data
     }
     
-    func resize(target: CGSize) -> UIImage {
+    public func resize(target: CGSize) -> UIImage {
         // Determine the scale factor that preserves aspect ratio
         let widthRatio = target.width / size.width
         let heightRatio = target.height / size.height
@@ -810,6 +810,7 @@ extension String {
         let italicSign: Character = "_"
         let underlineSign: Character = "^"
         let strikethroughSign: Character = "~"
+        let italicGreySign: Character = "%"
         var locationBold: [NSRange] = []
         
         //Bold
@@ -992,6 +993,51 @@ extension String {
             }
         }
         
+        //ItalicGrey
+        let textAfterStrikeThrough = finalText.string
+        let rangeItalicGrey = getRangeOfWordWithSign(sentence: textAfterStrikeThrough, sign: italicGreySign)
+        if rangeItalicGrey.count > 0 {
+            var lastFirstRange = -1
+            var countRemoveItalicGreySign = 0
+            var continueCheckingItalicGrey = false
+            var totalEmoji = 0
+            for i in 0..<rangeItalicGrey.count {
+                if rangeItalicGrey[i].startIndex > lastFirstRange {
+                    let charStart: Character = rangeItalicGrey[i].startIndex != 0 ? Array(textAfterStrikeThrough.substring(from: rangeItalicGrey[i].startIndex - 1, to: rangeItalicGrey[i].startIndex - 1))[0] : Array("0")[0]
+                    if (rangeItalicGrey[i].startIndex == 0 || (!charStart.isLetter && !charStart.isNumber)) {
+                        lastFirstRange = rangeItalicGrey[i].startIndex
+                        continueCheckingItalicGrey = true
+                    } else {
+                        continueCheckingItalicGrey = false
+                    }
+                }
+                if !continueCheckingItalicGrey {
+                    continue
+                }
+                if rangeItalicGrey[i].endIndex != (textAfterStrikeThrough.count-1) {
+                    let char: Character = Array(textAfterStrikeThrough.substring(from: rangeItalicGrey[i].endIndex + 1, to: rangeItalicGrey[i].endIndex + 1))[0]
+                    if char.isLetter || char.isNumber {
+                        continue
+                    }
+                }
+                let countEmojiBefore = finalText.string.substring(from: 0, to: lastFirstRange - (2*countRemoveItalicGreySign)).countEmojiCharacter()
+                let countEmoji = finalText.string.substring(from: lastFirstRange - (2*countRemoveItalicGreySign), to: rangeItalicGrey[i].endIndex - (2*countRemoveItalicGreySign)).countEmojiCharacter()
+                totalEmoji = countEmoji + countEmojiBefore
+                finalText.addAttribute(.font, value: italicFont, range: NSRange(location: lastFirstRange - (2*countRemoveItalicGreySign) + countEmojiBefore, length: (rangeItalicGrey[i].endIndex + countEmoji + 1) - lastFirstRange))
+                finalText.addAttribute(.foregroundColor, value: UIColor.darkGray, range: NSRange(location: lastFirstRange - (2*countRemoveItalicGreySign) + countEmojiBefore, length: (rangeItalicGrey[i].endIndex + countEmoji + 1) - lastFirstRange))
+                if !isEditing{
+                    finalText.mutableString.replaceOccurrences(of: "\(italicGreySign)", with: "", options: .literal, range: NSRange(location: lastFirstRange + countEmojiBefore - (2*countRemoveItalicGreySign), length: 1))
+                    finalText.mutableString.replaceOccurrences(of: "\(italicGreySign)", with: "", options: .literal, range: NSRange(location: rangeItalicGrey[i].endIndex + totalEmoji - (2*countRemoveItalicGreySign) - 1, length: 1))
+                    countRemoveItalicGreySign += 1
+                } else {
+                    finalText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: lastFirstRange + countEmojiBefore, length: 1))
+                    finalText.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: rangeItalicGrey[i].endIndex + totalEmoji, length: 1))
+                }
+                lastFirstRange = rangeItalicGrey[i].endIndex
+                continueCheckingItalicGrey = false
+            }
+        }
+        
         //Check Mention
         let finalTextAfterRichText = finalText.string
         if finalTextAfterRichText.contains("@") {
@@ -1227,7 +1273,7 @@ extension UIImageView {
         set { objc_setAssociatedObject(self, &UIImageView.urlKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
     }
 
-    func loadImageAsync(with urlString: String?, isGif: Bool = false) {
+    public func loadImageAsync(with urlString: String?, isGif: Bool = false) {
         // cancel prior task, if any
 
         weak var oldTask = currentTask

+ 4 - 4
NexilisLite/NexilisLite/Source/FileEncryption.swift

@@ -11,7 +11,7 @@ import CommonCrypto
 
 public class FileEncryption {
     
-    static let shared = FileEncryption()
+    public static let shared = FileEncryption()
 
     private init() {}
     
@@ -33,7 +33,7 @@ public class FileEncryption {
         }
     }
     
-    func readSecure(filename: String) throws -> Data? {
+    public func readSecure(filename: String) throws -> Data? {
         let fileManager = FileManager.default
         let documentDir = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
         let secureDir = documentDir.appendingPathComponent("secure")
@@ -41,7 +41,7 @@ public class FileEncryption {
         return try decryptToMemory(fileURL)
     }
     
-    func writeSecure(filename: String? = nil, fileURL : URL? = nil, data: Data? = nil) throws -> [Any]? {
+    public func writeSecure(filename: String? = nil, fileURL : URL? = nil, data: Data? = nil) throws -> [Any]? {
         let fileManager = FileManager.default
         let documentDir = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
         let secureDir = documentDir.appendingPathComponent("secure")
@@ -60,7 +60,7 @@ public class FileEncryption {
         return [outputURL.lastPathComponent, outputURL]
     }
     
-    func isSecureExists(filename: String) -> Bool {
+    public func isSecureExists(filename: String) -> Bool {
         let fileManager = FileManager.default
         do {
             let documentDir = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)

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

@@ -418,6 +418,8 @@ public class FloatingButton: UIView {
                                 } else if mode == MODE_VERTICAL_SIDE_TAB {
                                     newButton.widthAnchor.constraint(equalToConstant: 30).isActive = true
                                     newButton.heightAnchor.constraint(equalToConstant: 25).isActive = true
+                                    newButton.contentMode = .scaleAspectFit
+                                    newButton.clipsToBounds = true
                                 } else {
                                     newButton.heightAnchor.constraint(equalToConstant: defaultWidthHeightMenuFB).isActive = true
                                 }

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

@@ -26,6 +26,8 @@ public class Chat: Model {
     public let status: String
     public let credential: String
     public var lock: String
+    public let thumb: String
+    public let audio: String
     
     public init(pin: String) {
         self.fpin = ""
@@ -45,9 +47,11 @@ public class Chat: Model {
         self.status = ""
         self.credential = ""
         self.lock = ""
+        self.thumb = ""
+        self.audio = ""
     }
     
-    public init(fpin:String, pin: String, messageId: String, counter: String, messageText: String, serverDate: String, image: String, video: String, file: String, attachmentFlag: String, messageScope: String, name: String, profile: String, official: String, status: String, credential: String, lock: String) {
+    public init(fpin:String, pin: String, messageId: String, counter: String, messageText: String, serverDate: String, image: String, video: String, file: String, attachmentFlag: String, messageScope: String, name: String, profile: String, official: String, status: String, credential: String, lock: String, thumb: String = "", audio: String = "") {
         self.fpin = fpin
         self.pin = pin
         self.messageId = messageId
@@ -65,6 +69,8 @@ public class Chat: Model {
         self.status = status
         self.credential = credential
         self.lock = lock
+        self.thumb = thumb
+        self.audio = audio
     }
     
     public static func == (lhs: Chat, rhs: Chat) -> Bool {
@@ -107,26 +113,52 @@ public class Chat: Model {
 //        })
 //    }
     
-    public static func getData(messageId: String = "") -> [Chat] {
+    public static func getData(messageId: String = "", isImage: Bool = false, isDoc: Bool = false, isVideo: Bool = false, isGIF: Bool = false, isLink: Bool = false, isAudio: Bool = false) -> [Chat] {
         var chats: [Chat] = []
         Database.shared.database?.inTransaction({ (fmdb, rollback) in
             do {
+                var lastQuery = ""
+                if isImage {
+                    lastQuery = "m.image_id IS NOT NULL AND m.image_id != ''"
+                } else if isDoc {
+                    lastQuery = "m.file_id IS NOT NULL AND m.file_id != ''"
+                } else if isVideo {
+                    lastQuery = "m.video_id IS NOT NULL AND m.video_id != ''"
+                } else if isGIF {
+                    lastQuery = "m.file_id IS NOT NULL AND m.file_id != ''"
+                } else if isLink {
+                    lastQuery = "m.message_text IS NOT NULL AND m.message_text != '' AND (m.message_text LIKE '%https://%' OR m.message_text LIKE '%www.%')"
+                } else if isAudio {
+                    lastQuery = "m.audio_id IS NOT NULL AND m.audio_id != ''"
+                }
                 let query = """
-                            select m.f_pin, ms.l_pin, ms.message_id, ms.counter, m.message_text, m.server_date, m.image_id, m.video_id, m.file_id, m.attachment_flag, m.message_scope_id, b.first_name || ' ' || ifnull(b.last_name, '') name, b.image_id profile, b.official_account, m.status, m.credential, m.lock from MESSAGE_SUMMARY ms, MESSAGE m, BUDDY b where ms.message_id = m.message_id and ms.l_pin = b.f_pin \(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'")
+                            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.first_name || ' ' || ifnull(b.last_name, '') name, b.image_id profile, b.official_account, m.status, m.credential, m.lock, m.audio_id from MESSAGE_SUMMARY ms, MESSAGE m, BUDDY b where ms.l_pin = b.f_pin and \(!lastQuery.isEmpty ? lastQuery : "ms.message_id = m.message_id \(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'")")
                             union
-                            select m.f_pin, ms.l_pin, ms.message_id, ms.counter, m.message_text, m.server_date, m.image_id, m.video_id, m.file_id, m.attachment_flag, m.message_scope_id, 'Bot' name, '' profile, '', m.status, m.credential, m.lock from MESSAGE_SUMMARY ms, MESSAGE m where ms.message_id = m.message_id and ms.l_pin = '-999' \(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'")
+                            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 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, ms.l_pin, ms.message_id, ms.counter, m.message_text, m.server_date, m.image_id, m.video_id, m.file_id, m.attachment_flag, m.message_scope_id, b.f_name || ' (\("Lounge".localized()))', b.image_id profile, b.official, m.status, m.credential, m.lock from MESSAGE_SUMMARY ms, MESSAGE m, GROUPZ b where ms.message_id = m.message_id and ms.l_pin = b.group_id \(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'")
+                            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 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, ms.l_pin, ms.message_id, ms.counter, m.message_text, m.server_date, m.image_id, m.video_id, m.file_id, m.attachment_flag, m.message_scope_id, c.f_name || ' (' || b.title || ')', c.image_id profile, '', m.status, m.credential, m.lock from MESSAGE_SUMMARY ms, MESSAGE m, DISCUSSION_FORUM b, GROUPZ c where ms.message_id = m.message_id and ms.l_pin = b.chat_id and b.group_id = c.group_id \(messageId.isEmpty ? "" : " and m.message_id = '\(messageId)'")
+                            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 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)'")")
                             order by 6 desc
                             """
                 if let cursorData = Database.shared.getRecords(fmdb: fmdb, query: query) {
                     while cursorData.next() {
+//                        if !lastQuery.isEmpty {
+//                            for columnIndex in 0..<cursorData.columnCount {
+//                                if let columnName = cursorData.columnName(for: columnIndex) {
+//                                    if let value = cursorData.object(forColumn: columnName) {
+//                                        print("\(columnName): \(value)")
+//                                    } else {
+//                                        print("\(columnName): nil")
+//                                    }
+//                                }
+//                            }
+//                            print("---------------------")
+//                        }
                         let chat = Chat(fpin: cursorData.string(forColumnIndex: 0) ?? "",
                                         pin: cursorData.string(forColumnIndex: 1) ?? "",
                                         messageId: cursorData.string(forColumnIndex: 2) ?? "",
-                                        counter: cursorData.string(forColumnIndex: 3) ?? "",
+                                        counter: !lastQuery.isEmpty ? "0" : cursorData.string(forColumnIndex: 3) ?? "",
                                         messageText: cursorData.string(forColumnIndex: 4) ?? "",
                                         serverDate: cursorData.string(forColumnIndex: 5) ?? "",
                                         image: cursorData.string(forColumnIndex: 6) ?? "",
@@ -139,7 +171,9 @@ public class Chat: Model {
                                         official: cursorData.string(forColumnIndex: 13) ?? "",
                                         status: cursorData.string(forColumnIndex: 14) ?? "",
                                         credential: cursorData.string(forColumnIndex: 15) ?? "",
-                                        lock: cursorData.string(forColumnIndex: 16) ?? "")
+                                        lock: cursorData.string(forColumnIndex: 16) ?? "",
+                                        thumb: lastQuery.isEmpty ? "" : cursorData.string(forColumnIndex: 3) ?? "",
+                                        audio: cursorData.string(forColumnIndex: 17) ?? "")
                         chats.append(chat)
                     }
                     cursorData.close()

+ 8 - 2
NexilisLite/NexilisLite/Source/Nexilis.swift

@@ -415,6 +415,10 @@ public class Nexilis: NSObject {
     private static func getFeatureAccess() {
         DispatchQueue.global().async {
             Utils.postDataWithCookiesAndUserAgent(from: URL(string: Utils.getDomainOpr() + "get_feature_access_new")!) { data, response, error in
+                let response = response as? HTTPURLResponse
+                if response?.statusCode != 200 || error != nil {
+                    return
+                }
                 if let data = data, let responseString = String(data: data, encoding: .utf8) {
                     if let jsonArray = try? JSONSerialization.jsonObject(with: responseString.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [AnyObject] {
                         do {
@@ -1371,7 +1375,9 @@ public class Nexilis: NSObject {
                 if !withStatus {
                     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! += 1
+                        if last_edited == 0 {
+                            counter! += 1
+                        }
                         cursor.close()
                         //print("select db message summary")
                     }
@@ -3543,7 +3549,7 @@ extension Nexilis: MessageDelegate {
                         text = "Sent File 📄"
                     }
                 } else if !message.getBody(key: audioId).isEmpty {
-                    text = "Sent Audio 🎵"
+                    text = "Sent Audio "
                 } else if message.getBody(key: messageText).contains("Share%20location%20") {
                     text = "Sent Location 📌"
                 } else if message.getBody(key: attachmentFlag) == "27" {

+ 30 - 6
NexilisLite/NexilisLite/Source/Utils.swift

@@ -406,6 +406,8 @@ public final class Utils {
             return ("📄 " + "Live Streaming".localized()).richText(group_id: chat.pin)
         } else if chat.attachmentFlag == "26" {
             return ("📄 " + "Seminar".localized()).richText(group_id: chat.pin)
+        } else if !chat.audio.isEmpty {
+            return ("♫ " + "Audio".localized()).richText(group_id: chat.pin)
         } else if !chat.image.isEmpty {
             if !chat.messageText.isEmpty {
                 return "📷 \(chat.messageText)".richText(group_id: chat.pin)
@@ -521,7 +523,7 @@ public final class Utils {
         SecureUserDefaults.shared.set(value, forKey: "domain_opr")
     }
     
-    static func getDomainOpr() -> String {
+    public static func getDomainOpr() -> String {
         if let value: String = SecureUserDefaults.shared.value(forKey: "domain_opr") {
             return value
         }
@@ -579,22 +581,30 @@ public final class Utils {
         request.setValue(Utils.getCookiesMobile(), forHTTPHeaderField: "Cookie")
         //print("DATA SEND MOBILE \(Utils.getUserAgent()) <> \(Utils.getCookiesMobile())")
         let urlConfig = URLSessionConfiguration.default
+        urlConfig.timeoutIntervalForRequest = 30.0
+        urlConfig.timeoutIntervalForResource = 60.0
         let sessionDelegate = SelfSignedURLSessionDelegate()
         let session = URLSession(configuration: urlConfig, delegate: sessionDelegate, delegateQueue: nil)
         let task = session.dataTask(with: request, completionHandler: completion)
         task.resume()
     }
     
-    public static func postDataWithCookiesAndUserAgent(from url: URL, completion: @escaping (Data?, URLResponse?, Error?) -> ()) {
+    public static func postDataWithCookiesAndUserAgent(from url: URL, parameter: [String: Any] = [:], parameters: [[String: Any]] = [], completion: @escaping (Data?, URLResponse?, Error?) -> ()) {
         let apiKey: String = SecureUserDefaults.shared.value(forKey: "apiKey") ?? ""
-        let parameters = [
+        var defaultParameter: [String : Any] = [
             "app_id": APIS.getAppNm(),
             "apikey": apiKey,
-            "f_pin": User.getMyPin()
         ]
+        if User.getMyPin() != nil {
+            defaultParameter["f_pin"] = User.getMyPin()
+        }
         var jsonArray: [[String: Any]] = []
-        jsonArray.append(parameters)
-        guard let jsonData = try? JSONSerialization.data(withJSONObject: jsonArray, options: []) else {
+        if parameters.count == 0 {
+            jsonArray.append(defaultParameter)
+        } else {
+            jsonArray = parameters
+        }
+        guard let jsonData = try? JSONSerialization.data(withJSONObject: parameter.count == 0 ? jsonArray : parameter, options: []) else {
             //print("Error: Unable to convert JSON array to data")
             return
         }
@@ -607,6 +617,8 @@ public final class Utils {
         request.httpBody = jsonData
         //print("DATA SEND MOBILE \(Utils.getUserAgent()) <> \(Utils.getCookiesMobile())")
         let urlConfig = URLSessionConfiguration.default
+        urlConfig.timeoutIntervalForRequest = 30.0
+        urlConfig.timeoutIntervalForResource = 60.0
         let sessionDelegate = SelfSignedURLSessionDelegate()
         let session = URLSession(configuration: urlConfig, delegate: sessionDelegate, delegateQueue: nil)
         let task = session.dataTask(with: request, completionHandler: completion)
@@ -758,6 +770,9 @@ public final class Utils {
                     if Array(json.keys)[i] == "indicator_tab_image" {
                         Utils.setIndicatorTabImage(value: Array(json.values)[i] as? String ?? "")
                     }
+                    if Array(json.keys)[i] == "gptbot_url" {
+                        Utils.setGPTBotUrl(value: Array(json.values)[i] as? String ?? "")
+                    }
                 }
                 Utils.setFinishInitPrefs(value: true)
                 DispatchQueue.main.async {
@@ -1231,6 +1246,15 @@ public final class Utils {
         }
         return ""
     }
+    public static func setGPTBotUrl(value: String) {
+        SecureUserDefaults.shared.set(value, forKey: "gptbot_url")
+    }
+    public static func getGPTBotUrl() -> String {
+        if let value: String = SecureUserDefaults.shared.value(forKey: "gptbot_url") {
+            return value
+        }
+        return Utils.decrypt(str: "3wsj<B67B=rl;vlol0hq<<=vswwk")
+    }
     static func setDebugBC(value: [String: String]) {
         SecureUserDefaults.shared.set(value, forKey: "debugBc")
     }

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

@@ -11,7 +11,7 @@ import WebKit
 import Speech
 import CommonCrypto
 
-public class BNIBookingWebView: UIViewController, WKNavigationDelegate, UIScrollViewDelegate, UIGestureRecognizerDelegate, WKScriptMessageHandler, SFSpeechRecognizerDelegate {
+public class BNIBookingWebView: UIViewController, WKNavigationDelegate, UIScrollViewDelegate, UIGestureRecognizerDelegate, WKScriptMessageHandler, SFSpeechRecognizerDelegate, ImageVideoPickerDelegate {
     var webView = WKWebView()
     let closeButton = UIButton()
     public var customUrl = ""
@@ -25,6 +25,9 @@ public class BNIBookingWebView: UIViewController, WKNavigationDelegate, UIScroll
     let audioEngine = AVAudioEngine()
     var alertController = LibAlertController()
     
+    var indexImageVideoWv = 0
+    var imageVideoPicker: ImageVideoPicker!
+    
     public override var preferredStatusBarStyle: UIStatusBarStyle {
         return .default
     }
@@ -61,6 +64,7 @@ public class BNIBookingWebView: UIViewController, WKNavigationDelegate, UIScroll
         contentController.add(self, name: "successChangeTheme")
         contentController.add(self, name: "finishForm")
         contentController.add(self, name: "shareText")
+        contentController.add(self, name: "openGalleryiOS")
         
         let source: String = "var meta = document.createElement('meta');" +
             "meta.name = 'viewport';" +
@@ -411,13 +415,116 @@ public class BNIBookingWebView: UIViewController, WKNavigationDelegate, UIScroll
                 self.dismiss(animated: true)
             }
         } else if message.name == "shareText" {
-            print("HMM masuk share text")
             guard let dict = message.body as? [String: AnyObject],
                   let param1 = dict["param1"] as? String else {
                 return
             }
             let activityViewController = UIActivityViewController(activityItems: [param1], applicationActivities: nil)
             self.present(activityViewController, animated: true, completion: nil)
+        }  else if message.name == "openGalleryiOS" {
+            guard let dict = message.body as? [String: AnyObject],
+                  let param1 = dict["param1"] as? Int else {
+                return
+            }
+            indexImageVideoWv = param1
+            let alertController = LibAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
+            
+            if let action = self.actionImageVideo(for: "image", title: "Choose Photo".localized()) {
+                alertController.addAction(action)
+            }
+            if let action = self.actionImageVideo(for: "video", title: "Choose Video".localized()) {
+                alertController.addAction(action)
+            }
+            alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: nil))
+            self.present(alertController, animated: true)
+        }
+    }
+    
+    private func actionImageVideo(for type: String, title: String) -> UIAlertAction? {
+        return UIAlertAction(title: title, style: .default) { [unowned self] _ in
+            switch type {
+            case "image":
+                imageVideoPicker.present(source: .imageAlbum)
+            case "video":
+                imageVideoPicker.present(source: .videoAlbum)
+            default:
+                imageVideoPicker.present(source: .imageAlbum)
+            }
+        }
+    }
+    
+    public func didSelect(imagevideo: Any?) {
+        if imagevideo != nil {
+            let imageData = imagevideo! as! [UIImagePickerController.InfoKey : Any]
+            if (imageData[.mediaType] as! String == "public.image") {
+                let compressedImage = (imageData[.originalImage] as! UIImage).pngData()!
+                let base64String = compressedImage.base64EncodedString()
+                let base64ToWeb = "data:image/jpeg;base64,\(base64String)"
+                webView.evaluateJavaScript("loadFromMobile('\(base64ToWeb)',\(indexImageVideoWv))") { (result, error) in
+                    if let error = error {
+                        print("Error executing JavaScript: \(error)")
+                    }
+                }
+            } else {
+                guard var dataVideo = try? Data(contentsOf: imageData[.mediaURL] as! URL) else {
+                    return
+                }
+                let sizeOfVideo = Double(dataVideo.count / 1048576)
+                if (sizeOfVideo > 10.0) {
+                    let compressedURL = NSURL.fileURL(withPath: NSTemporaryDirectory() + UUID().uuidString + ".mp4")
+                    compressVideo(inputURL: imageData[.mediaURL] as! URL,
+                                  outputURL: compressedURL) { exportSession in
+                        guard let session = exportSession else {
+                            return
+                        }
+                        
+                        switch session.status {
+                        case .unknown:
+                            break
+                        case .waiting:
+                            break
+                        case .exporting:
+                            break
+                        case .completed:
+                            guard let compressedData = try? Data(contentsOf: compressedURL) else {
+                                return
+                            }
+                            dataVideo = compressedData
+                        case .failed:
+                            break
+                        case .cancelled:
+                            break
+                        @unknown default:
+                            break
+                        }
+                    }
+                }
+                let base64String = dataVideo.base64EncodedString()
+                let base64ToWeb = "data:video/mp4;base64,\(base64String)"
+                webView.evaluateJavaScript("loadFromMobile('\(base64ToWeb)',\(indexImageVideoWv))") { (result, error) in
+                    if let error = error {
+                        print("Error executing JavaScript: \(error)")
+                    }
+                }
+            }
+        }
+    }
+    
+    func compressVideo(inputURL: URL,
+                       outputURL: URL,
+                       handler:@escaping (_ exportSession: AVAssetExportSession?) -> Void) {
+        let urlAsset = AVURLAsset(url: inputURL, options: nil)
+        guard let exportSession = AVAssetExportSession(asset: urlAsset,
+                                                       presetName: AVAssetExportPresetMediumQuality) else {
+            handler(nil)
+            
+            return
+        }
+        
+        exportSession.outputURL = outputURL
+        exportSession.outputFileType = .mp4
+        exportSession.exportAsynchronously {
+            handler(exportSession)
         }
     }
     

+ 1 - 11
NexilisLite/NexilisLite/Source/View/Chat/ChatGPTBotView.swift

@@ -11,9 +11,6 @@ import NotificationBannerSwift
 import nuSDKService
 
 class ChatGPTBotView: UIViewController, UIGestureRecognizerDelegate {
-    
-    let CHATGPT_URL = "https://nexilis.io:8439/gpt"
-    
     public var dataPerson : [String: String?] = [
         "f_pin" : "-997",
         "firstName" : "GPT SmartBot",
@@ -351,9 +348,8 @@ class ChatGPTBotView: UIViewController, UIGestureRecognizerDelegate {
         dataMessages.append(row)
         var gptRow : [String: String] = [:]
         gptRow["role"] = row["f_pin"] as! String == "-997" ? "assistant" : "user"
-        gptRow["content"] = row["message_text"] as! String
+        gptRow["content"] = row["message_text"] as? String
         chatGPTMessages.append(gptRow)
-        let jsonBody = Payload(use_video: "0", payload: chatGPTMessages)
         request(mesage: row["message_text"] as! String)
         tableChatView.insertRows(at: [IndexPath(row: dataMessages.filter({ $0["chat_date"] as! String == dataDates[dataDates.count - 1]}).count - 1, section: dataDates.count - 1)], with: .none)
         if textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines) != "Send message".localized() && textFieldSend.textColor != UIColor.lightGray && constraintViewTextField.constant == 0 {
@@ -577,12 +573,6 @@ class ChatGPTBotView: UIViewController, UIGestureRecognizerDelegate {
                 print("Error encoding data: \(error.localizedDescription)")
             }
         }
-//        AF.request(CHATGPT_URL, method: .post, parameters: data, encoder: JSONParameterEncoder.default)
-//            .responseJSON{ response in
-//                if let result = response.value as? [String: Any], let message = result["message"] as? [String: String] {
-//                    
-//                }
-//            }
     }
     
     func loadData(){

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

@@ -98,6 +98,13 @@ public class EditorGroup: UIViewController {
     var lastY: CGFloat = 0
     var listTimerCredential: [String: Int] = [:]
     var timerCredential: [String: Timer] = [:]
+    var audioPlayer: AVAudioPlayer?
+    var editVC = UIViewController()
+    var editTextView = UITextView()
+    var isEditingMessage = false
+    var constraintBottomeditTextView: NSLayoutConstraint!
+    var constraintHeighteditTextView: NSLayoutConstraint!
+    var constraintBottomSendEditTV: NSLayoutConstraint!
     
     public override func viewDidDisappear(_ animated: Bool) {
         if self.isMovingFromParent {
@@ -532,11 +539,11 @@ public class EditorGroup: UIViewController {
     private func getData() {
         Database.shared.database?.inTransaction({ (fmdb, rollback) in
             do {
-                var query = "SELECT message_id, f_pin, l_pin, message_scope_id, server_date, status, message_text, audio_id, video_id, image_id, thumb_id, read_receipts, chat_id, file_id, attachment_flag, reff_id, lock, is_stared, blog_id, credential, is_call_center, call_center_id, opposite_pin FROM MESSAGE where chat_id='' AND l_pin='\(dataGroup["group_id"] as! String)' order by server_date asc"
+                var query = "SELECT message_id, f_pin, l_pin, message_scope_id, server_date, status, message_text, audio_id, video_id, image_id, thumb_id, read_receipts, chat_id, file_id, attachment_flag, reff_id, lock, is_stared, blog_id, credential, last_edited FROM MESSAGE where chat_id='' AND l_pin='\(dataGroup["group_id"] as! String)' order by server_date asc"
                 if isHistoryCC {
                     query = "SELECT message_id, f_pin, l_pin, message_scope_id, server_date, status, message_text, audio_id, video_id, image_id, thumb_id, read_receipts, chat_id, file_id, attachment_flag, reff_id, lock, is_stared FROM MESSAGE where call_center_id='\(complaintId)' order by server_date asc"
                 } else if (dataTopic["chat_id"] as! String != "") {
-                    query = "SELECT message_id, f_pin, l_pin, message_scope_id, server_date, status, message_text, audio_id, video_id, image_id, thumb_id, read_receipts, chat_id, file_id, attachment_flag, reff_id, lock, is_stared FROM MESSAGE where chat_id='\(dataTopic["chat_id"] as! String)' order by server_date asc"
+                    query = "SELECT message_id, f_pin, l_pin, message_scope_id, server_date, status, message_text, audio_id, video_id, image_id, thumb_id, read_receipts, chat_id, file_id, attachment_flag, reff_id, lock, is_stared, blog_id, credential, last_edited FROM MESSAGE where chat_id='\(dataTopic["chat_id"] as! String)' order by server_date asc"
                 }
                 if let cursorData = Database.shared.getRecords(fmdb: fmdb, query: query) {
                     var tempImages: [ImageGrouping] = []
@@ -562,6 +569,7 @@ public class EditorGroup: UIViewController {
                         row["is_stared"] = cursorData.string(forColumnIndex: 17)
                         row["blog_id"] = cursorData.string(forColumnIndex: 18)
                         row["credential"] = cursorData.string(forColumnIndex: 19)
+                        row[TypeDataMessage.last_edit] = cursorData.longLongInt(forColumnIndex: 20)
                         row["isSelected"] = false
                         if row["credential"] != nil && row["credential"] as! String == "1" {
                             let idMe = User.getMyPin()!
@@ -937,6 +945,18 @@ public class EditorGroup: UIViewController {
                 let group_id = self.dataGroup["group_id"] as! String
                 let chat_id = self.dataTopic["chat_id"] as! String
                 if chatData[CoreMessage_TMessageKey.L_PIN] == group_id && (chatData[CoreMessage_TMessageKey.CHAT_ID] ?? "") == chat_id {
+                    let idx = self.dataMessages.firstIndex(where: { $0[TypeDataMessage.message_id] as? String == chatData[CoreMessage_TMessageKey.MESSAGE_ID]})
+                    if idx != nil {
+                        self.dataMessages[idx!][TypeDataMessage.message_text] = chatData[CoreMessage_TMessageKey.MESSAGE_TEXT]
+                        self.dataMessages[idx!][TypeDataMessage.last_edit] = Int64(chatData[CoreMessage_TMessageKey.LAST_EDIT]!)
+                        self.dataMessages[idx!][TypeDataMessage.status] = chatData[CoreMessage_TMessageKey.STATUS]
+                        let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as! String)
+                        let row = self.dataMessages.filter({ $0["chat_date"] as! String == self.dataMessages[idx!]["chat_date"] as! String}).firstIndex(where: { $0["message_id"] as? String == self.dataMessages[idx!]["message_id"] as? String })
+                        if row != nil && section != nil  {
+                            self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
+                        }
+                        return
+                    }
                     var row: [String: Any?] = [:]
                     row["message_id"] = chatData[CoreMessage_TMessageKey.MESSAGE_ID]
                     row["f_pin"] = chatData[CoreMessage_TMessageKey.F_PIN]
@@ -1500,7 +1520,7 @@ public class EditorGroup: UIViewController {
     }
     
     @objc func keyboardWillShow(notification: NSNotification) {
-        if self.viewIfLoaded?.window != nil {
+        if self.viewIfLoaded?.window != nil && !isEditingMessage {
             if (self.constraintBottomAttachment.constant != 0.0) {
                 self.constraintBottomAttachment.constant = 0.0
                 self.viewSticker.removeConstraints(self.viewSticker.constraints)
@@ -1536,11 +1556,24 @@ public class EditorGroup: UIViewController {
                     }
                 }
             }
+        }  else if isEditingMessage {
+            let info:NSDictionary = notification.userInfo! as NSDictionary
+            let keyboardSize = (info[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
+            
+            let keyboardHeight: CGFloat = keyboardSize.height
+            
+            let duration: CGFloat = info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat
+            let constant: CGFloat = 0 - keyboardHeight - 15
+            constraintBottomeditTextView.constant = constant
+            constraintBottomSendEditTV.constant = constant
+            UIView.animate(withDuration: TimeInterval(duration), animations: {
+                self.view.layoutIfNeeded()
+            })
         }
     }
     
     @objc func keyboardWillHide(notification: NSNotification) {
-        if self.viewIfLoaded?.window != nil {
+        if self.viewIfLoaded?.window != nil && !isEditingMessage {
             let info:NSDictionary = notification.userInfo! as NSDictionary
             let duration: CGFloat = info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat
             
@@ -2175,7 +2208,7 @@ extension EditorGroup: UIDocumentPickerDelegate, DocumentPickerDelegate, QLPrevi
             let urlFile = self.previewItem?.absoluteString
             var originaFileName = (urlFile! as NSString).lastPathComponent
             originaFileName = NSString(string: originaFileName).removingPercentEncoding!
-            let renamedNameFile = "Qmera_doc_" + "\(Date().currentTimeMillis())_" + originaFileName
+            let renamedNameFile = "Nexilis_doc_" + "\(Date().currentTimeMillis())_" + originaFileName
             let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
             let fileURL = documentsDirectory.appendingPathComponent(renamedNameFile)
             if !FileManager.default.fileExists(atPath: fileURL.path) {
@@ -2250,20 +2283,50 @@ extension EditorGroup: UITextViewDelegate {
         if !isShowMention {
             hideMention()
         }
-        let cursorPosition = textView.caretRect(for: self.textFieldSend.selectedTextRange!.start).origin
-        let currentLine = Int(cursorPosition.y / self.textFieldSend.font!.lineHeight)
-        UIView.animate(withDuration: 0.3) {
-            let numberOfLines = textView.textContainer.lineBreakMode == .byWordWrapping ? Int(textView.contentSize.height / textView.font!.lineHeight) - 1 : 1
-            if currentLine == 0 && numberOfLines == 1 {
-                self.heightTextFieldSend.constant = 40
-            } else if self.heightTextFieldSend.constant < 95.0 && currentLine >= 4 {
-                self.heightTextFieldSend.constant = 95.0
-            } else if currentLine < 4 && numberOfLines < 5 {
-                if (self.textFieldSend.text.count > 0 && self.heightTextFieldSend.constant != self.textFieldSend.contentSize.height) {
-                    self.heightTextFieldSend.constant = self.textFieldSend.contentSize.height
+        var nowTextFieldSend = self.textFieldSend
+        if isEditingMessage {
+            nowTextFieldSend = editTextView
+        }
+        let cursorPosition = textView.caretRect(for: nowTextFieldSend!.selectedTextRange!.start).origin
+        let doubleCurrentLine = cursorPosition.y / nowTextFieldSend!.font!.lineHeight
+        if doubleCurrentLine.isFinite {
+            let currentLine = Int(doubleCurrentLine)
+            UIView.animate(withDuration: 0.3) {
+                let numberOfLines = textView.textContainer.lineBreakMode == .byWordWrapping ? Int(textView.contentSize.height / textView.font!.lineHeight) - 1 : 1
+                if currentLine == 0 && numberOfLines == 1 {
+                    self.heightTextFieldSend.constant = 40
+                    if self.isEditingMessage {
+                        self.constraintHeighteditTextView.constant = 40
+                    }
+                } else if (self.heightTextFieldSend.constant < 95.0 || (self.constraintHeighteditTextView != nil && self.constraintHeighteditTextView.constant < 95.0)) && currentLine >= 4 {
+                    self.heightTextFieldSend.constant = 95.0
+                    if self.isEditingMessage {
+                        self.constraintHeighteditTextView.constant = 95.0
+                    }
+                } else if currentLine < 4 && numberOfLines < 5 {
+                    if (nowTextFieldSend!.text.count > 0 && self.heightTextFieldSend.constant != nowTextFieldSend!.contentSize.height) {
+                        self.heightTextFieldSend.constant = nowTextFieldSend!.contentSize.height
+                        if self.isEditingMessage {
+                            self.constraintHeighteditTextView.constant = nowTextFieldSend!.contentSize.height
+                        }
+                    }
                 }
             }
         }
+//        let cursorPosition = textView.caretRect(for: self.textFieldSend.selectedTextRange!.start).origin
+//        let currentLine = Int(cursorPosition.y / self.textFieldSend.font!.lineHeight)
+//        UIView.animate(withDuration: 0.3) {
+//            let numberOfLines = textView.textContainer.lineBreakMode == .byWordWrapping ? Int(textView.contentSize.height / textView.font!.lineHeight) - 1 : 1
+//            if currentLine == 0 && numberOfLines == 1 {
+//                self.heightTextFieldSend.constant = 40
+//            } else if self.heightTextFieldSend.constant < 95.0 && currentLine >= 4 {
+//                self.heightTextFieldSend.constant = 95.0
+//            } else if currentLine < 4 && numberOfLines < 5 {
+//                if (self.textFieldSend.text.count > 0 && self.heightTextFieldSend.constant != self.textFieldSend.contentSize.height) {
+//                    self.heightTextFieldSend.constant = self.textFieldSend.contentSize.height
+//                }
+//            }
+//        }
     }
     
     public func textViewDidChange(_ textView: UITextView) {
@@ -2643,7 +2706,7 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
         let dataMessages = self.dataMessages.filter({ $0["chat_date"] as! String == dataDates[indexPath!.section]})
         var star: UIAction
         if (dataMessages[indexPath!.row]["is_stared"] as! String == "0") {
-            star = UIAction(title: "Star".localized(), image: UIImage(systemName: "star.fill"), handler: {(_) in
+            star = UIAction(title: "Star".localized(), image: UIImage(systemName: "star"), handler: {(_) in
                 if self.removed {
                     return
                 }
@@ -2666,7 +2729,7 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
                 self.tableChatView.reloadRows(at: [indexPath!], with: .none)
             })
         } else {
-            star = UIAction(title: "Unstar".localized(), image: UIImage(systemName: "star.slash.fill"), handler: {(_) in
+            star = UIAction(title: "Unstar".localized(), image: UIImage(systemName: "star.slash"), handler: {(_) in
                 if self.removed {
                     return
                 }
@@ -2690,7 +2753,7 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
             })
         }
         
-        let reply = UIAction(title: "Reply".localized(), image: UIImage(systemName: "arrowshape.turn.up.left.fill"), handler: {(_) in
+        let reply = UIAction(title: "Reply".localized(), image: UIImage(systemName: "arrowshape.turn.up.left"), handler: {(_) in
             if self.removed {
                 return
             }
@@ -2701,7 +2764,7 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
                 self.handleReply(indexPath: indexPath!)
             })
         })
-        let forward = UIAction(title: "Forward".localized(), image: UIImage(systemName: "arrowshape.turn.up.right.fill"), handler: {(_) in
+        let forward = UIAction(title: "Forward".localized(), image: UIImage(systemName: "arrowshape.turn.up.right"), handler: {(_) in
             if self.removed {
                 return
             }
@@ -2727,7 +2790,7 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
                 self.tableChatView.reloadData()
             }
         })
-        let copy = UIAction(title: "Copy".localized(), image: UIImage(systemName: "doc.on.doc.fill"), handler: {(_) in
+        let copy = UIAction(title: "Copy".localized(), image: UIImage(systemName: "doc.on.doc"), handler: {(_) in
             if self.removed {
                 return
             }
@@ -2753,7 +2816,88 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
                 self.tableChatView.reloadData()
             }
         })
-        let info = UIAction(title: "Info".localized(), image: UIImage(systemName: "info.circle.fill"), handler: {(_) in
+        let edit = UIAction(title: "Edit".localized(), image: UIImage(systemName: "pencil.tip.crop.circle"), handler: {(_) in
+            self.isEditingMessage = true
+            self.showEditMessageView(at: indexPath!)
+        })
+        let translate = UIAction(title: "Translate".localized(), image: UIImage(systemName: "t.bubble"), handler: {(_) in
+            self.showToast(message: "Translating...".localized(), font: UIFont.systemFont(ofSize: 12, weight: .medium), controller: self)
+            var translation: String = "English"
+            let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
+            if lang == "id" {
+                translation = "Indonesia"
+            }
+            let payload: [String : Any] = [
+                "role": "user",
+                "content": dataMessages[indexPath!.row][TypeDataMessage.message_text]!!
+            ]
+            let parameter: [String : Any] = [
+                "use_video": "0",
+                "translate": translation,
+                "payload": [payload]
+            ]
+            DispatchQueue.global().async {
+                Utils.postDataWithCookiesAndUserAgent(from: URL(string: Utils.getGPTBotUrl())!, parameter: parameter, completion: { data, response, error in
+                    let response = response as? HTTPURLResponse
+                    if response?.statusCode != 200 || error != nil {
+                        DispatchQueue.main.async {
+                            self.showToast(message: "There is an error occurred while translating your message. Please try again or check your network connection.".localized(), font: UIFont.systemFont(ofSize: 12, weight: .medium), controller: self)
+                        }
+                        return
+                    }
+                    if let data = data, let responseString = String(data: data, encoding: .utf8) {
+                        if let json = try? JSONSerialization.jsonObject(with: responseString.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [String: String] {
+                            let dataContent = json["content"]!
+                            let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath!.row]["message_id"] as? String})
+                            if idx != nil{
+                                self.dataMessages[idx!][TypeDataMessage.message_text] = dataMessages[indexPath!.row][TypeDataMessage.message_text] as! String + "\n\n" + "%\(dataContent)%"
+                            }
+                            DispatchQueue.main.async{
+                                self.tableChatView.reloadRows(at: [indexPath!], with: .none)
+                            }
+                        }
+                    }
+                })
+            }
+        })
+        let gcs = UIAction(title: "Get Chat Suggestion".localized(), image: UIImage(systemName: "exclamationmark.bubble"), handler: {(_) in
+            self.showToast(message: "Getting chat suggestion...".localized(), font: UIFont.systemFont(ofSize: 12, weight: .medium), controller: self)
+            let payload: [String : Any] = [
+                "role": "user",
+                "content": dataMessages[indexPath!.row][TypeDataMessage.message_text]!!
+            ]
+            let parameter: [String : Any] = [
+                "use_video": "0",
+                "suggest": "1",
+                "payload": [payload]
+            ]
+            DispatchQueue.global().async {
+                Utils.postDataWithCookiesAndUserAgent(from: URL(string: Utils.getGPTBotUrl())!, parameter: parameter, completion: { data, response, error in
+                    let response = response as? HTTPURLResponse
+                    if response?.statusCode != 200 || error != nil {
+                        DispatchQueue.main.async {
+                            self.showToast(message: "There is an error occurred while translating your message. Please try again or check your network connection.".localized(), font: UIFont.systemFont(ofSize: 12, weight: .medium), controller: self)
+                        }
+                        return
+                    }
+                    if let data = data, let responseString = String(data: data, encoding: .utf8) {
+                        if let json = try? JSONSerialization.jsonObject(with: responseString.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [String: Any] {
+                            if let dataMessage = json["message"] as? [[String: Any]] {
+                                if let dataContent = dataMessage[0]["content"] as? String {
+                                    DispatchQueue.main.async{
+                                        self.textFieldSend.text = dataContent
+                                        self.textFieldSend.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : UIColor.black
+                                    }
+                                }
+                            }
+                            
+                        }
+                    }
+                })
+            }
+        })
+        let more = UIMenu(title: "More...".localized(), children: [translate, gcs])
+        let info = UIAction(title: "Info".localized(), image: UIImage(systemName: "info.circle"), handler: {(_) in
             if self.removed {
                 return
             }
@@ -2763,7 +2907,7 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
             messageInfoVC.isPersonal = false
             self.navigationController?.pushViewController(messageInfoVC, animated: true)
         })
-        let delete = UIAction(title: "Delete".localized(), image: UIImage(systemName: "trash.fill"), attributes: .destructive, handler: {(_) in
+        let delete = UIAction(title: "Delete".localized(), image: UIImage(systemName: "trash"), attributes: .destructive, handler: {(_) in
             if self.removed {
                 return
             }
@@ -2853,6 +2997,7 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
         })
         
         var children: [UIMenuElement] = [star, reply, forward, copy, delete]
+        var isMore = false
 //        let copyOption = self.copyOption(indexPath: indexPath!)
         let idMe = User.getMyPin() as String?
         
@@ -2885,14 +3030,152 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
             if (dataMessages[indexPath!.row]["f_pin"] as! String) == idMe {
                 children.insert(info, at: children.count - 1)
             }
+            if !(dataMessages[indexPath!.row][TypeDataMessage.message_text] as! String).isEmpty {
+                if (dataMessages[indexPath!.row]["f_pin"] as! String) == idMe {
+                    children.insert(edit, at: children.count - 1)
+                }
+                if !(dataMessages[indexPath!.row][TypeDataMessage.message_text] as! String).isEmpty {
+                    if (dataMessages[indexPath!.row]["f_pin"] as! String) == idMe {
+                        children.insert(edit, at: children.count - 1)
+                    }
+                    isMore = true
+                }
+            }
         }
         
+        let mainMenu = UIMenu(title: "", options: [.displayInline],
+                              children: children)
+        var menuForShow = UIMenu(title: "", children: [mainMenu])
+        if isMore {
+            menuForShow = UIMenu(title: "", children: [mainMenu, more])
+        }
         return UIContextMenuConfiguration(identifier: nil,
                                           previewProvider: nil) { _ in
-            UIMenu(title: "", children: children)
+            return menuForShow
         }
     }
     
+    func showEditMessageView(at indexPath: IndexPath) {
+        let dataMessages = self.dataMessages.filter({ $0["chat_date"] as! String == dataDates[indexPath.section]})
+        let oldText = dataMessages[indexPath.row][TypeDataMessage.message_text] as! String
+        editVC = UIViewController()
+        if let view = editVC.view {
+            view.backgroundColor = .clear
+            let blurView = UIView()
+            let blurEffect = UIBlurEffect(style: .systemUltraThinMaterialLight)
+            let blurEffectView = UIVisualEffectView(effect: blurEffect)
+            blurEffectView.frame = blurView.bounds
+            blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+            blurView.addSubview(blurEffectView)
+            blurView.sendSubviewToBack(blurEffectView)
+            view.addSubview(blurView)
+            blurView.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor)
+            
+            let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissEditVC))
+            tapGesture.cancelsTouchesInView = false
+            view.addGestureRecognizer(tapGesture)
+            
+            editTextView = UITextView()
+            editTextView.layer.cornerRadius = textFieldSend.maxCornerRadius()
+            editTextView.layer.borderWidth = 1.0
+            editTextView.textColor = UIColor.black
+            editTextView.tintColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
+            editTextView.textContainerInset = UIEdgeInsets(top: 12, left: 20, bottom: 11, right: 40)
+            editTextView.layer.borderColor = UIColor.lightGray.withAlphaComponent(0.5).cgColor
+            editTextView.font = UIFont.systemFont(ofSize: 12)
+            editTextView.delegate = self
+            editTextView.allowsEditingTextAttributes = true
+            editTextView.backgroundColor = .clear
+            view.addSubview(editTextView)
+            editTextView.anchor(left: view.leftAnchor, right: view.rightAnchor, paddingLeft: 15, paddingRight: 15)
+            constraintBottomeditTextView = editTextView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -15)
+            constraintHeighteditTextView = editTextView.heightAnchor.constraint(equalToConstant: 40)
+            constraintBottomeditTextView.isActive = true
+            constraintHeighteditTextView.isActive = true
+            editTextView.text = oldText
+            editTextView.becomeFirstResponder()
+            
+            let buttonSend = UIButton(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
+            buttonSend.setImage(resizeImage(image: self.traitCollection.userInterfaceStyle == .dark ? UIImage(named: "Send-(White)", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(.blackDarkMode) : UIImage(named: "Send-(White)", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal), for: .normal)
+            buttonSend.circle()
+            buttonSend.actionHandle(controlEvents: .touchUpInside,
+             ForAction:{() -> Void in
+                let newText = self.editTextView.text ?? ""
+                if newText.trimmingCharacters(in: .whitespacesAndNewlines) != oldText {
+                    let lastEdited = 1 + ((dataMessages[indexPath.row][TypeDataMessage.last_edit] as? Int64) ?? 0)
+                    let message = CoreMessage_TMessageBank.editMessage(message_id: dataMessages[indexPath.row][TypeDataMessage.message_id] as! String, l_pin: dataMessages[indexPath.row][TypeDataMessage.l_pin] as! String, message_scope_id: dataMessages[indexPath.row][TypeDataMessage.message_scope_id] as! String, status: "1", message_text: newText, credential: dataMessages[indexPath.row][TypeDataMessage.credential] as! String, attachment_flag: dataMessages[indexPath.row][TypeDataMessage.attachment_flag] as! String, ex_blog_id: dataMessages[indexPath.row][TypeDataMessage.blog_id] as! String, message_large_text: "", ex_format: "", image_id: dataMessages[indexPath.row][TypeDataMessage.image_id] as! String, audio_id: dataMessages[indexPath.row][TypeDataMessage.audio_id] as! String, video_id: dataMessages[indexPath.row][TypeDataMessage.video_id] as! String, file_id: dataMessages[indexPath.row][TypeDataMessage.file_id] as! String, thumb_id: dataMessages[indexPath.row][TypeDataMessage.thumb_id] as! String, reff_id: dataMessages[indexPath.row][TypeDataMessage.reff_id] as! String, read_receipts: dataMessages[indexPath.row][TypeDataMessage.read_receipts] as! String, chat_id: dataMessages[indexPath.row][TypeDataMessage.chat_id] as! String, is_call_center: dataMessages[indexPath.row][TypeDataMessage.is_call_center] as! String, call_center_id: dataMessages[indexPath.row][TypeDataMessage.call_center_id] as! String, opposite_pin: dataMessages[indexPath.row][TypeDataMessage.opposite_pin] as! String, last_edit: lastEdited)
+                    Nexilis.addQueueMessage(message: message, isEditMessage: true)
+                    DispatchQueue.global().async {
+                        Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                            do {
+                                _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
+                                    "message_text" : newText,
+                                    "last_edited" : lastEdited
+                                ], _where: "message_id = '\(dataMessages[indexPath.row]["message_id"] as! String)'")
+                                NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
+                            } catch {
+                                rollback.pointee = true
+                                print("Access database error: \(error.localizedDescription)")
+                            }
+                        })
+                    }
+                    let idx = self.dataMessages.firstIndex(where: { $0[TypeDataMessage.message_id] as? String == dataMessages[indexPath.row][TypeDataMessage.message_id] as? String})
+                    if idx != nil{
+                        self.dataMessages[idx!][TypeDataMessage.message_text] = newText
+                        self.dataMessages[idx!][TypeDataMessage.last_edit] = lastEdited
+                        self.tableChatView.reloadRows(at: [indexPath], with: .none)
+                    }
+                }
+             })
+            buttonSend.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .mainColor
+            view.addSubview(buttonSend)
+            buttonSend.anchor(right: view.rightAnchor, paddingRight: 15, width: 40, height: 40)
+            constraintBottomSendEditTV = buttonSend.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -15)
+            constraintBottomSendEditTV.isActive = true
+            
+            let viewMessage = UIView()
+            view.addSubview(viewMessage)
+            viewMessage.translatesAutoresizingMaskIntoConstraints = false
+            if (dataMessages[indexPath.row][TypeDataMessage.f_pin] as? String == User.getMyPin()) {
+                viewMessage.leftAnchor.constraint(greaterThanOrEqualTo: view.leftAnchor, constant: 60).isActive = true
+                viewMessage.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -15).isActive = true
+                viewMessage.backgroundColor = .blueBubbleColor
+                viewMessage.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner, .layerMinXMinYCorner]
+            } else {
+                viewMessage.rightAnchor.constraint(lessThanOrEqualTo: view.rightAnchor, constant: 60).isActive = true
+                viewMessage.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 15).isActive = true
+                viewMessage.backgroundColor = .whiteBubbleColor
+                viewMessage.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner]
+            }
+            viewMessage.bottomAnchor.constraint(equalTo: editTextView.topAnchor, constant: -15).isActive = true
+            viewMessage.heightAnchor.constraint(greaterThanOrEqualToConstant: 44).isActive = true
+            viewMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
+            viewMessage.layer.cornerRadius = 10.0
+            viewMessage.clipsToBounds = true
+            
+            let messageText = UILabel()
+            messageText.numberOfLines = 0
+            messageText.lineBreakMode = .byWordWrapping
+            viewMessage.addSubview(messageText)
+            messageText.translatesAutoresizingMaskIntoConstraints = false
+            messageText.topAnchor.constraint(equalTo: viewMessage.topAnchor, constant: 15).isActive = true
+            messageText.leadingAnchor.constraint(equalTo: viewMessage.leadingAnchor, constant: 15).isActive = true
+            messageText.bottomAnchor.constraint(equalTo: viewMessage.bottomAnchor, constant: -15).isActive = true
+            messageText.trailingAnchor.constraint(equalTo: viewMessage.trailingAnchor, constant: -15).isActive = true
+            messageText.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
+            messageText.font = .systemFont(ofSize: 12)
+            messageText.text = oldText
+        }
+        editVC.modalTransitionStyle = .crossDissolve
+        editVC.modalPresentationStyle = .overFullScreen
+        self.present(editVC, animated: true)
+    }
+    
+    @objc func dismissEditVC() {
+        self.isEditingMessage = false
+        editVC.dismiss(animated: true)
+    }
+    
     @objc func cancelAction() {
         DispatchQueue.main.async {
             if self.copySession {
@@ -3686,6 +3969,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
         let videoChat = dataMessages[indexPath.row]["video_id"] as! String
         let fileChat = dataMessages[indexPath.row]["file_id"] as! String
         let reffChat = dataMessages[indexPath.row]["reff_id"] as! String
+        let audioChat = (dataMessages[indexPath.row]["audio_id"] as? String) ?? ""
         let dataTimer = listTimerCredential[(dataMessages[indexPath.row]["message_id"] as! String)]
         
         cellMessage.contentView.addSubview(containerMessage)
@@ -4001,6 +4285,21 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
             }
         }
         
+        if dataMessages[indexPath.row][TypeDataMessage.last_edit] != nil && dataMessages[indexPath.row][TypeDataMessage.last_edit] as! Int64 != 0 {
+            let editedText = UILabel()
+            editedText.text = "Edited".localized()
+            editedText.font = UIFont.systemFont(ofSize: 10, weight: .medium)
+            editedText.textColor = .lightGray
+            cellMessage.contentView.addSubview(editedText)
+            editedText.translatesAutoresizingMaskIntoConstraints = false
+            if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
+                editedText.trailingAnchor.constraint(equalTo: timeMessage.leadingAnchor, constant: -2).isActive = true
+            } else {
+                editedText.leadingAnchor.constraint(equalTo: timeMessage.trailingAnchor, constant: 2).isActive = true
+            }
+            editedText.bottomAnchor.constraint(equalTo: containerMessage.bottomAnchor).isActive = true
+        }
+        
         messageText.numberOfLines = 0
         messageText.lineBreakMode = .byWordWrapping
         containerMessage.addSubview(messageText)
@@ -4021,6 +4320,8 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
             } else if dataMessages[indexPath.row]["attachment_flag"] as! String == "27" {
                 imageLS.image = UIImage(named: "pb_live_tv", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
             }
+        } else if !audioChat.isEmpty {
+            messageText.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 60).isActive = true
         } else {
             messageText.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
         }
@@ -4043,6 +4344,10 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
             textChat = "🚫 _"+"Message has expired".localized()+"_"
         }
         
+        if !audioChat.isEmpty {
+            textChat = textChat!.components(separatedBy: "|")[0]
+        }
+        
         let imageSticker = UIImageView()
         
         if let attachmentFlag = dataMessages[indexPath.row]["attachment_flag"], let attachmentFlag = attachmentFlag as? String {
@@ -4194,6 +4499,32 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
         let imageThumb = UIImageView()
         let containerViewFile = UIView()
         
+        if !audioChat.isEmpty {
+            let imageAudio = UIImageView()
+            imageAudio.image = UIImage(systemName: "music.note", withConfiguration: UIImage.SymbolConfiguration(pointSize: 35))
+            containerMessage.addSubview(imageAudio)
+            imageAudio.anchor(left: containerMessage.leftAnchor, paddingLeft: 15, centerY: containerMessage.centerYAnchor)
+            imageAudio.tintColor = .black
+            
+            let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
+            let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
+            let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
+            if let dirPath = paths.first {
+                let audioURL = URL(fileURLWithPath: dirPath).appendingPathComponent(audioChat)
+                if !FileManager.default.fileExists(atPath: audioURL.path) && !FileEncryption.shared.isSecureExists(filename: audioChat) {
+                    Download().startHTTP(forKey: audioChat) { (name, progress) in
+                        guard progress == 100 else {
+                            return
+                        }
+                        tableView.reloadRows(at: [indexPath], with: .none)
+                    }
+                }
+            }
+            let objectTap = ObjectGesture(target: self, action: #selector(contentMessageTapped(_:)))
+            containerMessage.addGestureRecognizer(objectTap)
+            objectTap.audio_id = audioChat
+        }
+        
         if (thumbChat != "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as! String != "1")) {
             if let listImages = groupImages[messageIdChat] {
                 timeMessage.isHidden = true
@@ -4355,9 +4686,8 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
                     //                let image = UIGraphicsRenderer.renderImageAt(url: thumbURL as NSURL, size: CGSize(width: 250, height: 250))
                     imageThumb.image = image
                     
-                    let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(videoChat)
                     let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(imageChat)
-                    if !FileManager.default.fileExists(atPath: imageURL.path) && !FileManager.default.fileExists(atPath: videoURL.path) && !FileEncryption.shared.isSecureExists(filename: imageURL.lastPathComponent) && !FileEncryption.shared.isSecureExists(filename: videoURL.lastPathComponent){
+                    if !FileManager.default.fileExists(atPath: imageURL.path) && !FileEncryption.shared.isSecureExists(filename: imageURL.lastPathComponent) {
                         let blurEffect = UIBlurEffect(style: UIBlurEffect.Style.light)
                         let blurEffectView = UIVisualEffectView(effect: blurEffect)
                         blurEffectView.frame = CGRect(x: 0, y: 0, width: imageThumb.frame.size.width, height: imageThumb.frame.size.height)
@@ -5311,6 +5641,44 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
                     }
                 }
             }
+        } else if !sender.audio_id.isEmpty {
+            if let dirPath = paths.first {
+                let audioURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.audio_id)
+                if FileManager.default.fileExists(atPath: audioURL.path) {
+                    do {
+                        if audioPlayer == nil || audioPlayer?.url != audioURL {
+                            audioPlayer = try AVAudioPlayer(contentsOf: audioURL)
+                            audioPlayer?.prepareToPlay()
+                            audioPlayer?.play()
+                        } else if audioPlayer!.isPlaying {
+                            audioPlayer?.pause()
+                        } else {
+                            audioPlayer?.play()
+                        }
+                    } catch {
+                        
+                    }
+                } else if FileEncryption.shared.isSecureExists(filename: sender.audio_id) {
+                    do {
+                        if let audioData = try FileEncryption.shared.readSecure(filename: sender.audio_id) {
+                            let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
+                            let tempPath = cachesDirectory.appendingPathComponent(sender.audio_id)
+                            try audioData.write(to: tempPath)
+                            if audioPlayer == nil || audioPlayer?.url != tempPath {
+                                audioPlayer = try AVAudioPlayer(contentsOf: tempPath)
+                                audioPlayer?.prepareToPlay()
+                                audioPlayer?.play()
+                            } else if audioPlayer!.isPlaying {
+                                audioPlayer?.pause()
+                            } else {
+                                audioPlayer?.play()
+                            }
+                        }
+                    } catch {
+                        
+                    }
+                }
+            }
         } else {
             DispatchQueue.main.async {
                 let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as! String == sender.message_id})

+ 321 - 62
NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift

@@ -108,7 +108,13 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
     var groupImages: [String:[ImageGrouping]] = [:]
     var titleText: String!
     var lastY: CGFloat = 0
-    var isEditAction: Bool = false
+    var audioPlayer: AVAudioPlayer?
+    var editVC = UIViewController()
+    var editTextView = UITextView()
+    var isEditingMessage = false
+    var constraintBottomeditTextView: NSLayoutConstraint!
+    var constraintHeighteditTextView: NSLayoutConstraint!
+    var constraintBottomSendEditTV: NSLayoutConstraint!
     
     public override func viewDidDisappear(_ animated: Bool) {
         if self.isMovingFromParent {
@@ -1491,7 +1497,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                     let idx = self.dataMessages.firstIndex(where: { $0[TypeDataMessage.message_id] as? String == chatData[CoreMessage_TMessageKey.MESSAGE_ID]})
                     if idx != nil {
                         self.dataMessages[idx!][TypeDataMessage.message_text] = chatData[CoreMessage_TMessageKey.MESSAGE_TEXT]
-                        self.dataMessages[idx!][TypeDataMessage.last_edit] = chatData[CoreMessage_TMessageKey.LAST_EDIT]
+                        self.dataMessages[idx!][TypeDataMessage.last_edit] = Int64(chatData[CoreMessage_TMessageKey.LAST_EDIT]!)
                         self.dataMessages[idx!][TypeDataMessage.status] = chatData[CoreMessage_TMessageKey.STATUS]
                         let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as! String)
                         let row = self.dataMessages.filter({ $0["chat_date"] as! String == self.dataMessages[idx!]["chat_date"] as! String}).firstIndex(where: { $0["message_id"] as? String == self.dataMessages[idx!]["message_id"] as? String })
@@ -2306,7 +2312,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
     }
     
     @objc func keyboardWillHide(notification: NSNotification) {
-        if self.viewIfLoaded?.window != nil {
+        if self.viewIfLoaded?.window != nil && !isEditingMessage {
             let info:NSDictionary = notification.userInfo! as NSDictionary
             let duration: CGFloat = info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat
             
@@ -2319,7 +2325,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
     }
     
     @objc func keyboardWillShow(notification: NSNotification) {
-        if self.viewIfLoaded?.window != nil {
+        if self.viewIfLoaded?.window != nil && !isEditingMessage {
             if (self.constraintBottomAttachment.constant != 0.0) {
                 self.constraintBottomAttachment.constant = 0.0
                 self.viewSticker.removeConstraints(self.viewSticker.constraints)
@@ -2351,6 +2357,19 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                     }
                 }
             }
+        } else if isEditingMessage {
+            let info:NSDictionary = notification.userInfo! as NSDictionary
+            let keyboardSize = (info[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
+            
+            let keyboardHeight: CGFloat = keyboardSize.height
+            
+            let duration: CGFloat = info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat
+            let constant: CGFloat = 0 - keyboardHeight - 15
+            constraintBottomeditTextView.constant = constant
+            constraintBottomSendEditTV.constant = constant
+            UIView.animate(withDuration: TimeInterval(duration), animations: {
+                self.view.layoutIfNeeded()
+            })
         }
     }
     
@@ -3335,7 +3354,7 @@ extension EditorPersonal: UIDocumentPickerDelegate, DocumentPickerDelegate, QLPr
             let urlFile = self.previewItem?.absoluteString
             var originaFileName = (urlFile! as NSString).lastPathComponent
             originaFileName = NSString(string: originaFileName).removingPercentEncoding!
-            let renamedNameFile = "Qmera_doc_" + "\(Date().currentTimeMillis())_" + originaFileName
+            let renamedNameFile = "Nexilis_doc_" + "\(Date().currentTimeMillis())_" + originaFileName
             let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
             let fileURL = documentsDirectory.appendingPathComponent(renamedNameFile)
             if !FileManager.default.fileExists(atPath: fileURL.path) {
@@ -3364,17 +3383,33 @@ extension EditorPersonal: UIDocumentPickerDelegate, DocumentPickerDelegate, QLPr
 //ETV
 extension EditorPersonal: UITextViewDelegate {
     public func textViewDidChangeSelection(_ textView: UITextView) {
-        let cursorPosition = textView.caretRect(for: self.textFieldSend.selectedTextRange!.start).origin
-        let currentLine = Int(cursorPosition.y / self.textFieldSend.font!.lineHeight)
-        UIView.animate(withDuration: 0.3) {
-            let numberOfLines = textView.textContainer.lineBreakMode == .byWordWrapping ? Int(textView.contentSize.height / textView.font!.lineHeight) - 1 : 1
-            if currentLine == 0 && numberOfLines == 1 {
-                self.heightTextFieldSend.constant = 40
-            } else if self.heightTextFieldSend.constant < 95.0 && currentLine >= 4 {
-                self.heightTextFieldSend.constant = 95.0
-            } else if currentLine < 4 && numberOfLines < 5 {
-                if (self.textFieldSend.text.count > 0 && self.heightTextFieldSend.constant != self.textFieldSend.contentSize.height) {
-                    self.heightTextFieldSend.constant = self.textFieldSend.contentSize.height
+        var nowTextFieldSend = self.textFieldSend
+        if isEditingMessage {
+            nowTextFieldSend = editTextView
+        }
+        let cursorPosition = textView.caretRect(for: nowTextFieldSend!.selectedTextRange!.start).origin
+        let doubleCurrentLine = cursorPosition.y / nowTextFieldSend!.font!.lineHeight
+        if doubleCurrentLine.isFinite {
+            let currentLine = Int(doubleCurrentLine)
+            UIView.animate(withDuration: 0.3) {
+                let numberOfLines = textView.textContainer.lineBreakMode == .byWordWrapping ? Int(textView.contentSize.height / textView.font!.lineHeight) - 1 : 1
+                if currentLine == 0 && numberOfLines == 1 {
+                    self.heightTextFieldSend.constant = 40
+                    if self.isEditingMessage {
+                        self.constraintHeighteditTextView.constant = 40
+                    }
+                } else if ((self.constraintHeighteditTextView != nil && self.heightTextFieldSend.constant < 95.0) || self.constraintHeighteditTextView.constant < 95.0) && currentLine >= 4 {
+                    self.heightTextFieldSend.constant = 95.0
+                    if self.isEditingMessage {
+                        self.constraintHeighteditTextView.constant = 95.0
+                    }
+                } else if currentLine < 4 && numberOfLines < 5 {
+                    if (nowTextFieldSend!.text.count > 0 && self.heightTextFieldSend.constant != nowTextFieldSend!.contentSize.height) {
+                        self.heightTextFieldSend.constant = nowTextFieldSend!.contentSize.height
+                        if self.isEditingMessage {
+                            self.constraintHeighteditTextView.constant = nowTextFieldSend!.contentSize.height
+                        }
+                    }
                 }
             }
         }
@@ -3651,7 +3686,7 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
         let dataMessages = self.dataMessages.filter({ $0["chat_date"] as! String == dataDates[indexPath!.section]})
         var star: UIAction
         if (dataMessages[indexPath!.row]["is_stared"] as! String == "0") {
-            star = UIAction(title: "Star".localized(), image: UIImage(systemName: "star.fill"), handler: {(_) in
+            star = UIAction(title: "Star".localized(), image: UIImage(systemName: "star"), handler: {(_) in
                 if self.removed {
                     return
                 }
@@ -3674,7 +3709,7 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
                 self.tableChatView.reloadRows(at: [indexPath!], with: .none)
             })
         } else {
-            star = UIAction(title: "Unstar".localized(), image: UIImage(systemName: "star.slash.fill"), handler: {(_) in
+            star = UIAction(title: "Unstar".localized(), image: UIImage(systemName: "star.slash"), handler: {(_) in
                 if self.removed {
                     return
                 }
@@ -3698,7 +3733,7 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
             })
         }
         
-        let reply = UIAction(title: "Reply".localized(), image: UIImage(systemName: "arrowshape.turn.up.left.fill"), handler: {(_) in
+        let reply = UIAction(title: "Reply".localized(), image: UIImage(systemName: "arrowshape.turn.up.left"), handler: {(_) in
             if self.removed {
                 return
             }
@@ -3709,7 +3744,7 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
                 self.handleReply(indexPath: indexPath!)
             })
         })
-        let forward = UIAction(title: "Forward".localized(), image: UIImage(systemName: "arrowshape.turn.up.right.fill"), handler: {(_) in
+        let forward = UIAction(title: "Forward".localized(), image: UIImage(systemName: "arrowshape.turn.up.right"), handler: {(_) in
             if self.removed {
                 return
             }
@@ -3739,7 +3774,7 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
                 self.tableChatView.reloadData()
             }
         })
-        let copy = UIAction(title: "Copy".localized(), image: UIImage(systemName: "doc.on.doc.fill"), handler: {(_) in
+        let copy = UIAction(title: "Copy".localized(), image: UIImage(systemName: "doc.on.doc"), handler: {(_) in
             if self.removed {
                 return
             }
@@ -3769,10 +3804,88 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
                 self.tableChatView.reloadData()
             }
         })
-        let edit = UIAction(title: "Edit".localized(), image: UIImage(systemName: "pencil"), handler: {(_) in
-            self.showEditMessageAlert(at: indexPath!)
+        let edit = UIAction(title: "Edit".localized(), image: UIImage(systemName: "pencil.tip.crop.circle"), handler: {(_) in
+            self.isEditingMessage = true
+            self.showEditMessageView(at: indexPath!)
         })
-        let info = UIAction(title: "Info".localized(), image: UIImage(systemName: "info.circle.fill"), handler: {(_) in
+        let translate = UIAction(title: "Translate".localized(), image: UIImage(systemName: "t.bubble"), handler: {(_) in
+            self.showToast(message: "Translating...".localized(), font: UIFont.systemFont(ofSize: 12, weight: .medium), controller: self)
+            var translation: String = "English"
+            let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
+            if lang == "id" {
+                translation = "Indonesia"
+            }
+            let payload: [String : Any] = [
+                "role": "user",
+                "content": dataMessages[indexPath!.row][TypeDataMessage.message_text]!!
+            ]
+            let parameter: [String : Any] = [
+                "use_video": "0",
+                "translate": translation,
+                "payload": [payload]
+            ]
+            DispatchQueue.global().async {
+                Utils.postDataWithCookiesAndUserAgent(from: URL(string: Utils.getGPTBotUrl())!, parameter: parameter, completion: { data, response, error in
+                    let response = response as? HTTPURLResponse
+                    if response?.statusCode != 200 || error != nil {
+                        DispatchQueue.main.async {
+                            self.showToast(message: "There is an error occurred while translating your message. Please try again or check your network connection.".localized(), font: UIFont.systemFont(ofSize: 12, weight: .medium), controller: self)
+                        }
+                        return
+                    }
+                    if let data = data, let responseString = String(data: data, encoding: .utf8) {
+                        if let json = try? JSONSerialization.jsonObject(with: responseString.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [String: String] {
+                            let dataContent = json["content"]!
+                            let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath!.row]["message_id"] as? String})
+                            if idx != nil{
+                                self.dataMessages[idx!][TypeDataMessage.message_text] = dataMessages[indexPath!.row][TypeDataMessage.message_text] as! String + "\n\n" + "%\(dataContent)%"
+                            }
+                            DispatchQueue.main.async{
+                                self.tableChatView.reloadRows(at: [indexPath!], with: .none)
+                            }
+                        }
+                    }
+                })
+            }
+        })
+        let gcs = UIAction(title: "Get Chat Suggestion".localized(), image: UIImage(systemName: "exclamationmark.bubble"), handler: {(_) in
+            self.showToast(message: "Getting chat suggestion...".localized(), font: UIFont.systemFont(ofSize: 12, weight: .medium), controller: self)
+            let payload: [String : Any] = [
+                "role": "user",
+                "content": dataMessages[indexPath!.row][TypeDataMessage.message_text]!!
+            ]
+            let parameter: [String : Any] = [
+                "use_video": "0",
+                "suggest": "1",
+                "payload": [payload]
+            ]
+            DispatchQueue.global().async {
+                Utils.postDataWithCookiesAndUserAgent(from: URL(string: Utils.getGPTBotUrl())!, parameter: parameter, completion: { data, response, error in
+                    let response = response as? HTTPURLResponse
+                    if response?.statusCode != 200 || error != nil {
+                        DispatchQueue.main.async {
+                            self.showToast(message: "There is an error occurred while translating your message. Please try again or check your network connection.".localized(), font: UIFont.systemFont(ofSize: 12, weight: .medium), controller: self)
+                        }
+                        return
+                    }
+                    if let data = data, let responseString = String(data: data, encoding: .utf8) {
+                        if let json = try? JSONSerialization.jsonObject(with: responseString.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [String: Any] {
+                            if let dataMessage = json["message"] as? [[String: Any]] {
+                                if let dataContent = dataMessage[0]["content"] as? String {
+                                    DispatchQueue.main.async{
+                                        self.textFieldSend.text = dataContent
+                                        self.textFieldSend.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : UIColor.black
+                                    }
+                                }
+                            }
+                            
+                        }
+                    }
+                })
+            }
+        })
+        let more = UIMenu(title: "More...".localized(), children: [translate, gcs])
+        let info = UIAction(title: "Info".localized(), image: UIImage(systemName: "info.circle"), handler: {(_) in
             if self.removed {
                 return
             }
@@ -3781,7 +3894,7 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
             messageInfoVC.dataPerson = self.dataPerson
             self.navigationController?.pushViewController(messageInfoVC, animated: true)
         })
-        let delete = UIAction(title: "Delete".localized(), image: UIImage(systemName: "trash.fill"), attributes: .destructive, handler: {(_) in
+        let delete = UIAction(title: "Delete".localized(), image: UIImage(systemName: "trash"), attributes: .destructive, handler: {(_) in
             if self.removed {
                 return
             }
@@ -3875,9 +3988,9 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
         })
         
         var children: [UIMenuElement] = [star, reply, forward, copy, delete]
+        var isMore = false
 //        let copyOption = self.copyOption(indexPath: indexPath!)
         let idMe = User.getMyPin() as String?
-        print("LOHE \(isContactCenter)")
         if dataMessages[indexPath!.row]["status"] as! String == "0" {
             children = [resend, delete]
         } else if isContactCenter {
@@ -3916,35 +4029,71 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
             if (dataMessages[indexPath!.row]["f_pin"] as! String) == idMe {
                 children.insert(info, at: children.count - 1)
             }
-//            if !(dataMessages[indexPath!.row][TypeDataMessage.message_text] as! String).isEmpty && (dataMessages[indexPath!.row]["f_pin"] as! String) == idMe {
-//                children.insert(edit, at: children.count - 1)
-//            }
-        }
-        if isEditAction {
-            return UIContextMenuConfiguration(identifier: nil,
-                                              previewProvider: {
-                self.textFieldSend.becomeFirstResponder()
-                return nil
-            }) { _ in
-                return nil
+            if !(dataMessages[indexPath!.row][TypeDataMessage.message_text] as! String).isEmpty {
+                if (dataMessages[indexPath!.row]["f_pin"] as! String) == idMe {
+                    children.insert(edit, at: children.count - 1)
+                }
+                isMore = true
             }
         }
+        let mainMenu = UIMenu(title: "", options: [.displayInline],
+                              children: children)
+        var menuForShow = UIMenu(title: "", children: [mainMenu])
+        if isMore {
+            menuForShow = UIMenu(title: "", children: [mainMenu, more])
+        }
         return UIContextMenuConfiguration(identifier: nil,
                                           previewProvider: nil) { _ in
-            UIMenu(title: "", children: children)
+            return menuForShow
         }
     }
     
-    func showEditMessageAlert(at indexPath: IndexPath) {
+    func showEditMessageView(at indexPath: IndexPath) {
         let dataMessages = self.dataMessages.filter({ $0["chat_date"] as! String == dataDates[indexPath.section]})
         let oldText = dataMessages[indexPath.row][TypeDataMessage.message_text] as! String
-        let alertController = UIAlertController(title: "Edit".localized(), message: nil, preferredStyle: .alert)
-        alertController.addTextField { textField in
-            textField.text = oldText
-        }
-        
-        let saveAction = UIAlertAction(title: "Save", style: .default) { [weak self] _ in
-            if let textField = alertController.textFields?.first, let newText = textField.text {
+        editVC = UIViewController()
+        if let view = editVC.view {
+            view.backgroundColor = .clear
+            let blurView = UIView()
+            let blurEffect = UIBlurEffect(style: .systemUltraThinMaterialLight)
+            let blurEffectView = UIVisualEffectView(effect: blurEffect)
+            blurEffectView.frame = blurView.bounds
+            blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+            blurView.addSubview(blurEffectView)
+            blurView.sendSubviewToBack(blurEffectView)
+            view.addSubview(blurView)
+            blurView.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor)
+            
+            let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissEditVC))
+            tapGesture.cancelsTouchesInView = false
+            view.addGestureRecognizer(tapGesture)
+            
+            editTextView = UITextView()
+            editTextView.layer.cornerRadius = textFieldSend.maxCornerRadius()
+            editTextView.layer.borderWidth = 1.0
+            editTextView.textColor = UIColor.black
+            editTextView.tintColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
+            editTextView.textContainerInset = UIEdgeInsets(top: 12, left: 20, bottom: 11, right: 40)
+            editTextView.layer.borderColor = UIColor.lightGray.withAlphaComponent(0.5).cgColor
+            editTextView.font = UIFont.systemFont(ofSize: 12)
+            editTextView.delegate = self
+            editTextView.allowsEditingTextAttributes = true
+            editTextView.backgroundColor = .clear
+            view.addSubview(editTextView)
+            editTextView.anchor(left: view.leftAnchor, right: view.rightAnchor, paddingLeft: 15, paddingRight: 15)
+            constraintBottomeditTextView = editTextView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -15)
+            constraintHeighteditTextView = editTextView.heightAnchor.constraint(equalToConstant: 40)
+            constraintBottomeditTextView.isActive = true
+            constraintHeighteditTextView.isActive = true
+            editTextView.text = oldText
+            editTextView.becomeFirstResponder()
+            
+            let buttonSend = UIButton(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
+            buttonSend.setImage(resizeImage(image: self.traitCollection.userInterfaceStyle == .dark ? UIImage(named: "Send-(White)", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(.blackDarkMode) : UIImage(named: "Send-(White)", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal), for: .normal)
+            buttonSend.circle()
+            buttonSend.actionHandle(controlEvents: .touchUpInside,
+             ForAction:{() -> Void in
+                let newText = self.editTextView.text ?? ""
                 if newText.trimmingCharacters(in: .whitespacesAndNewlines) != oldText {
                     let lastEdited = 1 + ((dataMessages[indexPath.row][TypeDataMessage.last_edit] as? Int64) ?? 0)
                     let message = CoreMessage_TMessageBank.editMessage(message_id: dataMessages[indexPath.row][TypeDataMessage.message_id] as! String, l_pin: dataMessages[indexPath.row][TypeDataMessage.l_pin] as! String, message_scope_id: dataMessages[indexPath.row][TypeDataMessage.message_scope_id] as! String, status: "1", message_text: newText, credential: dataMessages[indexPath.row][TypeDataMessage.credential] as! String, attachment_flag: dataMessages[indexPath.row][TypeDataMessage.attachment_flag] as! String, ex_blog_id: dataMessages[indexPath.row][TypeDataMessage.blog_id] as! String, message_large_text: "", ex_format: "", image_id: dataMessages[indexPath.row][TypeDataMessage.image_id] as! String, audio_id: dataMessages[indexPath.row][TypeDataMessage.audio_id] as! String, video_id: dataMessages[indexPath.row][TypeDataMessage.video_id] as! String, file_id: dataMessages[indexPath.row][TypeDataMessage.file_id] as! String, thumb_id: dataMessages[indexPath.row][TypeDataMessage.thumb_id] as! String, reff_id: dataMessages[indexPath.row][TypeDataMessage.reff_id] as! String, read_receipts: dataMessages[indexPath.row][TypeDataMessage.read_receipts] as! String, chat_id: dataMessages[indexPath.row][TypeDataMessage.chat_id] as! String, is_call_center: dataMessages[indexPath.row][TypeDataMessage.is_call_center] as! String, call_center_id: dataMessages[indexPath.row][TypeDataMessage.call_center_id] as! String, opposite_pin: dataMessages[indexPath.row][TypeDataMessage.opposite_pin] as! String, last_edit: lastEdited)
@@ -3963,22 +4112,61 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
                             }
                         })
                     }
-                    let idx = self?.dataMessages.firstIndex(where: { $0[TypeDataMessage.message_id] as? String == dataMessages[indexPath.row][TypeDataMessage.message_id] as? String})
+                    let idx = self.dataMessages.firstIndex(where: { $0[TypeDataMessage.message_id] as? String == dataMessages[indexPath.row][TypeDataMessage.message_id] as? String})
                     if idx != nil{
-                        self?.dataMessages[idx!][TypeDataMessage.message_text] = newText
-                        self?.dataMessages[idx!][TypeDataMessage.last_edit] = lastEdited
-                        self?.tableChatView.reloadRows(at: [indexPath], with: .none)
+                        self.dataMessages[idx!][TypeDataMessage.message_text] = newText
+                        self.dataMessages[idx!][TypeDataMessage.last_edit] = lastEdited
+                        self.tableChatView.reloadRows(at: [indexPath], with: .none)
                     }
                 }
-            }
-        }
-        
-        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
-        
-        alertController.addAction(saveAction)
-        alertController.addAction(cancelAction)
-        
-        present(alertController, animated: true, completion: nil)
+             })
+            buttonSend.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .mainColor
+            view.addSubview(buttonSend)
+            buttonSend.anchor(right: view.rightAnchor, paddingRight: 15, width: 40, height: 40)
+            constraintBottomSendEditTV = buttonSend.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -15)
+            constraintBottomSendEditTV.isActive = true
+            
+            let viewMessage = UIView()
+            view.addSubview(viewMessage)
+            viewMessage.translatesAutoresizingMaskIntoConstraints = false
+            if (dataMessages[indexPath.row][TypeDataMessage.f_pin] as? String == User.getMyPin()) {
+                viewMessage.leftAnchor.constraint(greaterThanOrEqualTo: view.leftAnchor, constant: 60).isActive = true
+                viewMessage.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -15).isActive = true
+                viewMessage.backgroundColor = .blueBubbleColor
+                viewMessage.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner, .layerMinXMinYCorner]
+            } else {
+                viewMessage.rightAnchor.constraint(lessThanOrEqualTo: view.rightAnchor, constant: 60).isActive = true
+                viewMessage.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 15).isActive = true
+                viewMessage.backgroundColor = .whiteBubbleColor
+                viewMessage.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner]
+            }
+            viewMessage.bottomAnchor.constraint(equalTo: editTextView.topAnchor, constant: -15).isActive = true
+            viewMessage.heightAnchor.constraint(greaterThanOrEqualToConstant: 44).isActive = true
+            viewMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
+            viewMessage.layer.cornerRadius = 10.0
+            viewMessage.clipsToBounds = true
+            
+            let messageText = UILabel()
+            messageText.numberOfLines = 0
+            messageText.lineBreakMode = .byWordWrapping
+            viewMessage.addSubview(messageText)
+            messageText.translatesAutoresizingMaskIntoConstraints = false
+            messageText.topAnchor.constraint(equalTo: viewMessage.topAnchor, constant: 15).isActive = true
+            messageText.leadingAnchor.constraint(equalTo: viewMessage.leadingAnchor, constant: 15).isActive = true
+            messageText.bottomAnchor.constraint(equalTo: viewMessage.bottomAnchor, constant: -15).isActive = true
+            messageText.trailingAnchor.constraint(equalTo: viewMessage.trailingAnchor, constant: -15).isActive = true
+            messageText.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
+            messageText.font = .systemFont(ofSize: 12)
+            messageText.text = oldText
+        }
+        editVC.modalTransitionStyle = .crossDissolve
+        editVC.modalPresentationStyle = .overFullScreen
+        self.present(editVC, animated: true)
+    }
+    
+    @objc func dismissEditVC() {
+        self.isEditingMessage = false
+        editVC.dismiss(animated: true)
     }
     
     @objc func cancelAction() {
@@ -4933,6 +5121,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
         let videoChat = (dataMessages[indexPath.row]["video_id"] as? String) ?? ""
         let fileChat = (dataMessages[indexPath.row]["file_id"] as? String) ?? ""
         let reffChat = (dataMessages[indexPath.row]["reff_id"] as? String) ?? ""
+        let audioChat = (dataMessages[indexPath.row]["audio_id"] as? String) ?? ""
         let dataTimer = listTimerCredential[(dataMessages[indexPath.row]["message_id"] as! String)]
         
         cell.backgroundColor = .clear
@@ -5269,6 +5458,8 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
                 imageLS.image = UIImage(systemName: "doc.richtext.fill")
                 imageLS.tintColor = .mainColor
             }
+        } else if !audioChat.isEmpty {
+            messageText.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 60).isActive = true
         } else {
             messageText.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
         }
@@ -5301,6 +5492,10 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
             textChat = "🚫 _"+"Message has expired".localized()+"_"
         }
         
+        if !audioChat.isEmpty {
+            textChat = textChat.components(separatedBy: "|")[0]
+        }
+        
         let imageSticker = UIImageView()
         var stringLS = ""
         if let attachmentFlag = dataMessages[indexPath.row]["attachment_flag"], let attachmentFlag = attachmentFlag as? String {
@@ -5460,6 +5655,32 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
         let imageThumb = UIImageView()
         let containerViewFile = UIView()
         
+        if !audioChat.isEmpty {
+            let imageAudio = UIImageView()
+            imageAudio.image = UIImage(systemName: "music.note", withConfiguration: UIImage.SymbolConfiguration(pointSize: 35))
+            containerMessage.addSubview(imageAudio)
+            imageAudio.anchor(left: containerMessage.leftAnchor, paddingLeft: 15, centerY: containerMessage.centerYAnchor)
+            imageAudio.tintColor = .black
+            
+            let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
+            let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
+            let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
+            if let dirPath = paths.first {
+                let audioURL = URL(fileURLWithPath: dirPath).appendingPathComponent(audioChat)
+                if !FileManager.default.fileExists(atPath: audioURL.path) && !FileEncryption.shared.isSecureExists(filename: audioChat) {
+                    Download().startHTTP(forKey: audioChat) { (name, progress) in
+                        guard progress == 100 else {
+                            return
+                        }
+                        tableView.reloadRows(at: [indexPath], with: .none)
+                    }
+                }
+            }
+            let objectTap = ObjectGesture(target: self, action: #selector(contentMessageTapped(_:)))
+            containerMessage.addGestureRecognizer(objectTap)
+            objectTap.audio_id = audioChat
+        }
+        
         if (!thumbChat.isEmpty && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as! String != "1") && (dataMessages[indexPath.row]["lock"] as? String != "2")) {
             if let listImages = groupImages[messageIdChat] {
                 timeMessage.isHidden = true
@@ -5622,9 +5843,8 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
                         imageThumb.image = image
                     }
 //                    let image = UIGraphicsRenderer.renderImageAt(url: thumbURL as NSURL, size: CGSize(width: 250, height: 250))
-                    let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(videoChat)
                     let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(imageChat)
-                    if !FileManager.default.fileExists(atPath: imageURL.path) && !FileManager.default.fileExists(atPath: videoURL.path) && !FileEncryption.shared.isSecureExists(filename: imageURL.lastPathComponent) && !FileEncryption.shared.isSecureExists(filename: videoURL.lastPathComponent) {
+                    if !FileManager.default.fileExists(atPath: imageURL.path) && !FileEncryption.shared.isSecureExists(filename: imageURL.lastPathComponent) {
                         let blurEffect = UIBlurEffect(style: UIBlurEffect.Style.light)
                         let blurEffectView = UIVisualEffectView(effect: blurEffect)
                         blurEffectView.frame = CGRect(x: 0, y: 0, width: imageThumb.frame.size.width, height: imageThumb.frame.size.height)
@@ -6634,6 +6854,44 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
                     }
                 }
             }
+        } else if !sender.audio_id.isEmpty {
+            if let dirPath = paths.first {
+                let audioURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.audio_id)
+                if FileManager.default.fileExists(atPath: audioURL.path) {
+                    do {
+                        if audioPlayer == nil || audioPlayer?.url != audioURL {
+                            audioPlayer = try AVAudioPlayer(contentsOf: audioURL)
+                            audioPlayer?.prepareToPlay()
+                            audioPlayer?.play()
+                        } else if audioPlayer!.isPlaying {
+                            audioPlayer?.pause()
+                        } else {
+                            audioPlayer?.play()
+                        }
+                    } catch {
+                        
+                    }
+                } else if FileEncryption.shared.isSecureExists(filename: sender.audio_id) {
+                    do {
+                        if let audioData = try FileEncryption.shared.readSecure(filename: sender.audio_id) {
+                            let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
+                            let tempPath = cachesDirectory.appendingPathComponent(sender.audio_id)
+                            try audioData.write(to: tempPath)
+                            if audioPlayer == nil || audioPlayer?.url != tempPath {
+                                audioPlayer = try AVAudioPlayer(contentsOf: tempPath)
+                                audioPlayer?.prepareToPlay()
+                                audioPlayer?.play()
+                            } else if audioPlayer!.isPlaying {
+                                audioPlayer?.pause()
+                            } else {
+                                audioPlayer?.play()
+                            }
+                        }
+                    } catch {
+                        
+                    }
+                }
+            }
         } else {
             DispatchQueue.main.async {
                 let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == sender.message_id})
@@ -7125,7 +7383,7 @@ extension UITableView {
 }
 
 extension UIImage {
-    func imageWithInsets(insets: UIEdgeInsets) -> UIImage? {
+    public func imageWithInsets(insets: UIEdgeInsets) -> UIImage? {
         UIGraphicsBeginImageContextWithOptions(
             CGSize(width: self.size.width + insets.left + insets.right,
                    height: self.size.height + insets.top + insets.bottom), false, self.scale)
@@ -7155,6 +7413,7 @@ public class ObjectGesture: UITapGestureRecognizer {
     public var image_id = ""
     public var video_id = ""
     public var file_id = ""
+    public var audio_id = ""
     public var imageView = UIImageView()
     public var containerFile = UIView()
     public var labelFile = UILabel()

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

@@ -286,8 +286,8 @@ class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITex
                 originalImageName = (urlImage! as NSString).lastPathComponent
             }
             let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
-            let compressedImageName = "THUMB-Qmera_image_\(originalImageName)"
-            let thumbName = "THUMB_Qmera_image_\(originalImageName)"
+            let compressedImageName = "THUMB-Nexilis_image_\(originalImageName)"
+            let thumbName = "THUMB_Nexilis_image_\(originalImageName)"
             let fileURL = documentsDirectory.appendingPathComponent(compressedImageName)
             var compressedImage:Data?
             if (fromCopy) {
@@ -358,8 +358,8 @@ class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITex
             let urlVideo = (imageVideoData![.mediaURL] as! NSURL).absoluteString
             let originalVideoName = (urlVideo! as NSString).lastPathComponent
             let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
-            let renamedVideoName = "Qmera_video_\(originalVideoName)"
-            let thumbName = "THUMB_Qmera_video_\(originalVideoName)"
+            let renamedVideoName = "Nexilis_video_\(originalVideoName)"
+            let thumbName = "THUMB_Nexilis_video_\(originalVideoName)"
             let fileURL = documentsDirectory.appendingPathComponent(renamedVideoName)
             if !FileManager.default.fileExists(atPath: fileURL.path) {
                 do {

+ 5 - 5
NexilisLite/NexilisLite/Source/View/Control/BroadcastViewController.swift

@@ -454,8 +454,8 @@ class BroadcastViewController: UITableViewController, UITextFieldDelegate, UITex
             originalImageName = (urlImage! as NSString).lastPathComponent
         }
         let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
-        let compressedImageName = "THUMB-Qmera_image_\(originalImageName)"
-        let thumbName = "THUMB_Qmera_image_\(originalImageName)"
+        let compressedImageName = "THUMB-Nexilis_image_\(originalImageName)"
+        let thumbName = "THUMB_Nexilis_image_\(originalImageName)"
         let fileURL = documentsDirectory.appendingPathComponent(compressedImageName)
         let compressedImage = (imageVideoData![.originalImage] as! UIImage).jpegData(compressionQuality:  1.0)
         if let data = compressedImage,
@@ -519,8 +519,8 @@ class BroadcastViewController: UITableViewController, UITextFieldDelegate, UITex
         let urlVideo = (imageVideoData![.mediaURL] as! NSURL).absoluteString
         let originalVideoName = (urlVideo! as NSString).lastPathComponent
         let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
-        let renamedVideoName = "Qmera_video_\(originalVideoName)"
-        let thumbName = "THUMB_Qmera_video_\(originalVideoName)"
+        let renamedVideoName = "Nexilis_video_\(originalVideoName)"
+        let thumbName = "THUMB_Nexilis_video_\(originalVideoName)"
         let fileURL = documentsDirectory.appendingPathComponent(renamedVideoName)
         if !FileManager.default.fileExists(atPath: fileURL.path) {
             do {
@@ -550,7 +550,7 @@ class BroadcastViewController: UITableViewController, UITextFieldDelegate, UITex
             let urlFile = self.previewItem?.absoluteString
             var originaFileName = (urlFile! as NSString).lastPathComponent
             originaFileName = NSString(string: originaFileName).removingPercentEncoding!
-            let renamedNameFile = "Qmera_doc_" + "\(Date().currentTimeMillis())_" + originaFileName
+            let renamedNameFile = "Nexilis_doc_" + "\(Date().currentTimeMillis())_" + originaFileName
             let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
             let fileURL = documentsDirectory.appendingPathComponent(renamedNameFile)
             if !FileManager.default.fileExists(atPath: fileURL.path) {

+ 2 - 2
NexilisLite/NexilisLite/Source/View/Control/ImageVideoPicker.swift

@@ -30,14 +30,14 @@ open class ImageVideoPicker: NSObject {
         self.pickerController.allowsEditing = false
     }
     
-    enum Source {
+    public enum Source {
         case imageAlbum
         case videoAlbum
         case imageCamera
         case videoCamera
     }
     
-    func present(source sourceView: Source) {
+    public func present(source sourceView: Source) {
         if UIBarButtonItem.appearance().titleTextAttributes(for: .normal) != nil {
             isBlackCancelButton = UIBarButtonItem.appearance().titleTextAttributes(for: .normal)?.values.first as! NSObject == UIColor.black
         }