Преглед изворни кода

update and fix for release 5.0.44

alqindiirsyam пре 2 месеци
родитељ
комит
36aea2ecde
25 измењених фајлова са 1890 додато и 781 уклоњено
  1. 4 4
      AppBuilder/AppBuilder.xcodeproj/project.pbxproj
  2. 2 2
      AppBuilder/AppBuilder/SecondTabViewController.swift
  3. 112 41
      AppBuilder/AppBuilderShare/ShareViewController.swift
  4. BIN
      NexilisLite/NexilisLite/.DS_Store
  5. BIN
      NexilisLite/NexilisLite/Resource/.DS_Store
  6. 21 0
      NexilisLite/NexilisLite/Resource/Assets.xcassets/pb_ic_attach_spc.imageset/Contents.json
  7. BIN
      NexilisLite/NexilisLite/Resource/Assets.xcassets/pb_ic_attach_spc.imageset/pb_ic_attach_spc.png
  8. 21 0
      NexilisLite/NexilisLite/Resource/Assets.xcassets/pb_ic_attach_spc_off.imageset/Contents.json
  9. BIN
      NexilisLite/NexilisLite/Resource/Assets.xcassets/pb_ic_attach_spc_off.imageset/pb_ic_attach_spc_off.png
  10. 37 20
      NexilisLite/NexilisLite/Resource/PreviewAttachmentImageVideo.xib
  11. BIN
      NexilisLite/NexilisLite/Source/.DS_Store
  12. 164 45
      NexilisLite/NexilisLite/Source/APIS.swift
  13. 2 1
      NexilisLite/NexilisLite/Source/CoreMessage_TMessageBank.swift
  14. 1 0
      NexilisLite/NexilisLite/Source/CoreMessage_TMessageKey.swift
  15. 5 1
      NexilisLite/NexilisLite/Source/Database.swift
  16. 19 12
      NexilisLite/NexilisLite/Source/Extension.swift
  17. 5 4
      NexilisLite/NexilisLite/Source/Nexilis.swift
  18. 311 0
      NexilisLite/NexilisLite/Source/Utils.swift
  19. BIN
      NexilisLite/NexilisLite/Source/View/.DS_Store
  20. 1 1
      NexilisLite/NexilisLite/Source/View/Chat/ChatGPTBotView.swift
  21. 376 195
      NexilisLite/NexilisLite/Source/View/Chat/EditorGroup.swift
  22. 404 186
      NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift
  23. 240 101
      NexilisLite/NexilisLite/Source/View/Chat/EditorStarMessages.swift
  24. 165 44
      NexilisLite/NexilisLite/Source/View/Chat/PreviewAttachmentImageVideo.swift
  25. 0 124
      NexilisLite/NexilisLite/Source/View/Chat/PreviewAttachmentImageVideo.xib

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

@@ -560,7 +560,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 5.0.43;
+				MARKETING_VERSION = 5.0.44;
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
@@ -596,7 +596,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 5.0.43;
+				MARKETING_VERSION = 5.0.44;
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
@@ -632,7 +632,7 @@
 					"@executable_path/../../Frameworks",
 				);
 				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
-				MARKETING_VERSION = 5.0.43;
+				MARKETING_VERSION = 5.0.44;
 				OTHER_CFLAGS = "-fstack-protector-strong";
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder.AppBuilderShare;
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -671,7 +671,7 @@
 					"@executable_path/../../Frameworks",
 				);
 				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
-				MARKETING_VERSION = 5.0.43;
+				MARKETING_VERSION = 5.0.44;
 				OTHER_CFLAGS = "-fstack-protector-strong";
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder.AppBuilderShare;
 				PRODUCT_NAME = "$(TARGET_NAME)";

+ 2 - 2
AppBuilder/AppBuilder/SecondTabViewController.swift

@@ -82,7 +82,7 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
         searchController.searchBar.setPositionAdjustment(UIOffset(horizontal: 10, vertical: 0), for: .search)
         searchController.searchBar.setCustomBackgroundImage(image: UIImage(named: self.traitCollection.userInterfaceStyle == .dark ? "nx_search_bar_dark" : "nx_search_bar", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!)
         searchController.obscuresBackgroundDuringPresentation = false
-        searchController.searchBar.searchTextField.attributedPlaceholder = NSAttributedString(string: "Search".localized() + "...", attributes: [NSAttributedString.Key.foregroundColor: UIColor.gray, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 11)])
+        searchController.searchBar.searchTextField.attributedPlaceholder = NSAttributedString(string: "Search".localized() + "...", attributes: [NSAttributedString.Key.foregroundColor: UIColor.gray, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 14)])
         return searchController
     }()
     
@@ -339,7 +339,7 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
         textViewSearch.placeholder = "Search".localized() + "..."
         textViewSearch.isUserInteractionEnabled = true
         imageViewSearch.addSubview(textViewSearch)
-        textViewSearch.font = .systemFont(ofSize: 11 + String.offset())
+        textViewSearch.font = .systemFont(ofSize: 14 + String.offset())
         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([

+ 112 - 41
AppBuilder/AppBuilderShare/ShareViewController.swift

@@ -32,6 +32,9 @@ class ShareViewController: UIViewController, UITableViewDelegate, UITableViewDat
     var selectedImageTypeImage: UIImage!
     private var previewView: VideoPreviewView?
     let previewController = QLPreviewController()
+    private var preparingView: UIView!
+    private var activityIndicator: UIActivityIndicatorView!
+    private var preparingLabel: UILabel!
     let nameGroupShare = "group.nexilis.share"
 
     override func viewDidLoad() {
@@ -48,6 +51,47 @@ class ShareViewController: UIViewController, UITableViewDelegate, UITableViewDat
         setupUI()
     }
     
+    private func setupPreparingOverlay() {
+        // full‐screen semi‐transparent background
+        preparingView = UIView(frame: view.bounds)
+        preparingView.backgroundColor = UIColor(white: 0, alpha: 0.5)
+        preparingView.isHidden = true
+        
+        // spinner
+        activityIndicator = UIActivityIndicatorView(style: .large)
+        activityIndicator.translatesAutoresizingMaskIntoConstraints = false
+        preparingView.addSubview(activityIndicator)
+        
+        // label
+        preparingLabel = UILabel()
+        preparingLabel.translatesAutoresizingMaskIntoConstraints = false
+        preparingLabel.text = "Preparing…"
+        preparingLabel.textColor = .white
+        preparingLabel.font = UIFont.systemFont(ofSize: 17, weight: .medium)
+        preparingView.addSubview(preparingLabel)
+        
+        vcHandleVideo.view.addSubview(preparingView)
+        
+        // constraints: center spinner, label below
+        NSLayoutConstraint.activate([
+            activityIndicator.centerXAnchor.constraint(equalTo: preparingView.centerXAnchor),
+            activityIndicator.centerYAnchor.constraint(equalTo: preparingView.centerYAnchor, constant: -10),
+            preparingLabel.topAnchor.constraint(equalTo: activityIndicator.bottomAnchor, constant: 12),
+            preparingLabel.centerXAnchor.constraint(equalTo: preparingView.centerXAnchor)
+        ])
+    }
+    
+    private func showPreparingOverlay(_ show: Bool) {
+        preparingView.isHidden = !show
+        if show {
+            activityIndicator.startAnimating()
+            self.view.isUserInteractionEnabled = false
+        } else {
+            activityIndicator.stopAnimating()
+            self.view.isUserInteractionEnabled = true
+        }
+    }
+    
     func setupUI() {
         let cancelButton = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(cancelAction))
         cancelButton.tintColor = .label
@@ -149,17 +193,25 @@ class ShareViewController: UIViewController, UITableViewDelegate, UITableViewDat
                 dataShared["idContact"] = selectedContact.id
                 dataShared["data"] = textView.text
                 if typeShareNum == TypeShare.image {
-                    let compressedImageName = "Nexilis_image_\(Int(Date().timeIntervalSince1970 * 1000))_\(selectedImage != nil ? selectedImage.lastPathComponent : "SS_Image")"
-                    let thumbName = "THUMB_Nexilis_image_\(Int(Date().timeIntervalSince1970 * 1000))_\(selectedImage != nil ? selectedImage.lastPathComponent : "SS_Image")"
+                    let compressedImageName = "Nexilis_image_\(Int(Date().timeIntervalSince1970 * 1000))_\(selectedImage != nil ? selectedImage.lastPathComponent.components(separatedBy: ".")[0] : "SS_Image").jpeg"
+                    let thumbName = "THUMB_Nexilis_image_\(Int(Date().timeIntervalSince1970 * 1000))_\(selectedImage != nil ? selectedImage.lastPathComponent.components(separatedBy: ".")[0] : "SS_Image").jpeg"
                     if let appGroupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: nameGroupShare) {
                         let sharedImageURL = appGroupURL.appendingPathComponent(compressedImageName)
                         let sharedThumbURL = appGroupURL.appendingPathComponent(thumbName)
                         if selectedImage != nil {
                             try? UIImage(contentsOfFile: selectedImage.path)?.jpegData(compressionQuality: 0.25)?.write(to: sharedThumbURL)
-                            try? UIImage(contentsOfFile: selectedImage.path)?.jpegData(compressionQuality: 0.5)?.write(to: sharedImageURL)
+                            if let dataImage = UIImage(contentsOfFile: selectedImage.path)?.jpegData(compressionQuality: 1.0) {
+                                if let compressed = compressImageLikeWhatsApp(UIImage(data: dataImage) ?? UIImage()) {
+                                    try? compressed.write(to: sharedImageURL)
+                                }
+                            }
                         } else {
                             try? selectedImageTypeImage?.jpegData(compressionQuality: 0.25)?.write(to: sharedThumbURL)
-                            try? selectedImageTypeImage?.jpegData(compressionQuality: 0.5)?.write(to: sharedImageURL)
+                            if let dataImage = selectedImageTypeImage?.jpegData(compressionQuality: 1.0) {
+                                if let compressed = compressImageLikeWhatsApp(UIImage(data: dataImage) ?? UIImage()) {
+                                    try? compressed.write(to: sharedImageURL)
+                                }
+                            }
                         }
                     }
                     dataShared["thumb"] = thumbName
@@ -178,42 +230,24 @@ class ShareViewController: UIViewController, UITableViewDelegate, UITableViewDat
                         )
                     }
                 } else if typeShareNum == TypeShare.video {
-                    let dataVideo = try? Data(contentsOf: selectedVideo)
-                    if let dataVideotoCompress = dataVideo {
-                        let sizeInKB = Double(dataVideotoCompress.count) / 1024.0
-                        let sizeOfVideo = sizeInKB / 1024.0
-                        if (sizeOfVideo > 10.0) {
-                            let compressedURL = NSURL.fileURL(withPath: NSTemporaryDirectory() + UUID().uuidString + ".mp4")
-                            compressVideo(inputURL: selectedVideo,
-                                          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
-                                    }
-                                    self.sendVideoToMainApp(compressedData, dataShared)
-                                case .failed:
-                                    break
-                                case .cancelled:
-                                    break
-                                @unknown default:
-                                    break
-                                }
-                            }
+                    showPreparingOverlay(true)
+                    let dispatchGroup = DispatchGroup()
+                    dispatchGroup.enter()
+                    let compressedURL = NSURL.fileURL(withPath: NSTemporaryDirectory() + UUID().uuidString + ".mp4")
+                    compressVideo(inputURL: selectedVideo,outputURL: compressedURL) { exportSession in
+                        guard let session = exportSession else {
                             return
-                        } else {
-                            self.sendVideoToMainApp(dataVideotoCompress, dataShared)
                         }
+                        if session.status == .completed {
+                            dispatchGroup.leave()
+                            guard let compressedData = try? Data(contentsOf: compressedURL) else {
+                                return
+                            }
+                            self.sendVideoToMainApp(compressedData, dataShared)
+                        }
+                    }
+                    dispatchGroup.notify(queue: .main) {
+                        self.showPreparingOverlay(false)
                     }
                 } else if typeShareNum == TypeShare.file || typeShareNum == TypeShare.audio {
                     let fileName = selectedFile.lastPathComponent
@@ -250,19 +284,22 @@ class ShareViewController: UIViewController, UITableViewDelegate, UITableViewDat
                         )
                     }
                 }
+                if typeShareNum != TypeShare.video {
+                    self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
+                }
             } catch {
                 
             }
         }
-        self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
+//        self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
     }
     
     private func sendVideoToMainApp(_ data: Data, _ dataShared: [String: Any]) {
         do {
             var dataShared = dataShared
             let originalVideoName = self.selectedVideo.lastPathComponent
-            let renamedVideoName = "Nexilis_video_\(Int(Date().timeIntervalSince1970 * 1000))_\(originalVideoName)"
-            let thumbName = "THUMB_Nexilis_video_\(Int(Date().timeIntervalSince1970 * 1000))_\(originalVideoName)"
+            let renamedVideoName = "Nexilis_video_\(Int(Date().timeIntervalSince1970 * 1000))_\(originalVideoName.components(separatedBy: ".")[0]).mp4"
+            let thumbName = "THUMB_Nexilis_video_\(Int(Date().timeIntervalSince1970 * 1000))_\(originalVideoName.components(separatedBy: ".")[0]).jpeg"
             if let appGroupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: nameGroupShare) {
                 let sharedVideoURL = appGroupURL.appendingPathComponent(renamedVideoName)
                 let sharedThumbURL = appGroupURL.appendingPathComponent(thumbName)
@@ -290,11 +327,44 @@ class ShareViewController: UIViewController, UITableViewDelegate, UITableViewDat
                     )
                 }
             }
+            self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
         } catch {
             
         }
     }
     
+    func compressImageLikeWhatsApp(_ image: UIImage, maxFileSizeMB: Double = 1.0, maxDimension: CGFloat = 1280) -> Data? {
+        let resizedImage = resizeImage(image: image, maxDimension: maxDimension)
+        var compressedData = resizedImage.jpegData(compressionQuality: 0.7) ?? Data()
+        var imageSizeMB = Double(compressedData.count) / (1024.0 * 1024.0)
+        
+        while imageSizeMB > maxFileSizeMB {
+            guard let tempImage = UIImage(data: compressedData) else { break }
+            compressedData = tempImage.jpegData(compressionQuality: 0.5) ?? compressedData
+            imageSizeMB = Double(compressedData.count) / (1024.0 * 1024.0)
+            print("Compressed to: \(imageSizeMB) MB")
+        }
+        
+        return compressedData
+    }
+    
+    func resizeImage(image: UIImage, maxDimension: CGFloat) -> UIImage {
+        let size = image.size
+        let aspectRatio = size.width / size.height
+        
+        var newSize: CGSize
+        if aspectRatio > 1 {
+            newSize = CGSize(width: maxDimension, height: maxDimension / aspectRatio)
+        } else {
+            newSize = CGSize(width: maxDimension * aspectRatio, height: maxDimension)
+        }
+
+        let renderer = UIGraphicsImageRenderer(size: newSize)
+        return renderer.image { _ in
+            image.draw(in: CGRect(origin: .zero, size: newSize))
+        }
+    }
+    
     func compressVideo(inputURL: URL,
                        outputURL: URL,
                        handler:@escaping (_ exportSession: AVAssetExportSession?) -> Void) {
@@ -612,6 +682,7 @@ class ShareViewController: UIViewController, UITableViewDelegate, UITableViewDat
                                 
                                 vcHandleVideo.modalPresentationStyle = .fullScreen
                                 self.navigationController?.present(vcHandleVideo, animated: true)
+                                setupPreparingOverlay()
                             }
                         }
                     }

BIN
NexilisLite/NexilisLite/.DS_Store


BIN
NexilisLite/NexilisLite/Resource/.DS_Store


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

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

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


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

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

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


+ 37 - 20
NexilisLite/NexilisLite/Resource/PreviewAttachmentImageVideo.xib

@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
     <device id="retina6_1" orientation="portrait" appearance="light"/>
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22684"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
         <capability name="Image references" minToolsVersion="12.0"/>
         <capability name="Safe area layout guides" minToolsVersion="9.0"/>
         <capability name="System colors in document resources" minToolsVersion="11.0"/>
@@ -15,10 +15,11 @@
                 <outlet property="buttonAckConfidential" destination="m6t-Me-xFW" id="36g-1Y-Jgr"/>
                 <outlet property="buttonCancel" destination="kDJ-TL-vNL" id="Xo6-YX-9ma"/>
                 <outlet property="buttonSend" destination="fNr-UI-Smq" id="m00-6U-HAF"/>
-                <outlet property="constraintButtonAckCondential" destination="svH-5g-qh7" id="9IB-9W-aPV"/>
-                <outlet property="constraintButtonSend" destination="CSE-Zv-Ou4" id="lHf-yS-zK1"/>
-                <outlet property="constraintLeftTextField" destination="zXq-IK-dyl" id="6cb-XR-bwZ"/>
-                <outlet property="constraintViewTextField" destination="2FC-An-lfS" id="T9O-u5-GtG"/>
+                <outlet property="buttonSpecFile" destination="HdB-8N-0l1" id="RyN-d9-wLZ"/>
+                <outlet property="constraintButtonAckCondential" destination="GWK-9A-opG" id="s7K-bT-3rj"/>
+                <outlet property="constraintButtonSend" destination="CSE-Zv-Ou4" id="6bU-Mw-auh"/>
+                <outlet property="constraintLeftTextField" destination="KXD-ok-nGH" id="dqn-N5-WLn"/>
+                <outlet property="constraintViewTextField" destination="nbp-vo-svJ" id="WnY-VM-DhU"/>
                 <outlet property="heightTextFieldSend" destination="4X4-Hj-TzA" id="BLV-O8-avl"/>
                 <outlet property="imagePreview" destination="1kP-2x-Lvs" id="CAj-mk-x5G"/>
                 <outlet property="scrollViewImage" destination="fUG-ls-mSU" id="u0l-KJ-SaX"/>
@@ -32,10 +33,10 @@
             <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
             <subviews>
                 <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" bounces="NO" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fUG-ls-mSU">
-                    <rect key="frame" x="0.0" y="48" width="414" height="814"/>
+                    <rect key="frame" x="0.0" y="96" width="414" height="732"/>
                     <subviews>
                         <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="1kP-2x-Lvs">
-                            <rect key="frame" x="0.0" y="0.0" width="414" height="814"/>
+                            <rect key="frame" x="0.0" y="0.0" width="414" height="732"/>
                             <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                         </imageView>
                     </subviews>
@@ -50,7 +51,7 @@
                     </constraints>
                 </scrollView>
                 <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="kDJ-TL-vNL" userLabel="Button Cancel">
-                    <rect key="frame" x="10" y="58" width="40" height="40"/>
+                    <rect key="frame" x="10" y="106" width="40" height="40"/>
                     <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
                     <constraints>
                         <constraint firstAttribute="width" constant="40" id="axp-R5-jBp"/>
@@ -61,8 +62,22 @@
                         <imageReference key="image" image="xmark" catalog="system" symbolScale="large"/>
                     </state>
                 </button>
+                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="HdB-8N-0l1" userLabel="Button Spec">
+                    <rect key="frame" x="364" y="106" width="40" height="40"/>
+                    <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
+                    <constraints>
+                        <constraint firstAttribute="width" relation="greaterThanOrEqual" constant="40" id="9Go-Ow-4El"/>
+                        <constraint firstAttribute="height" constant="40" id="PF5-22-e2F"/>
+                        <constraint firstAttribute="height" relation="greaterThanOrEqual" constant="40" id="f5A-df-wCC"/>
+                        <constraint firstAttribute="width" constant="40" id="fdB-IF-t1i"/>
+                    </constraints>
+                    <color key="tintColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                    <state key="normal">
+                        <imageReference key="image" image="pb_ic_attach_spc_off" symbolScale="large"/>
+                    </state>
+                </button>
                 <textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" showsHorizontalScrollIndicator="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="TU8-ei-nsO" customClass="CustomTextView" customModule="NexilisLite" customModuleProvider="target">
-                    <rect key="frame" x="65" y="802" width="329" height="40"/>
+                    <rect key="frame" x="65" y="768" width="329" height="40"/>
                     <constraints>
                         <constraint firstAttribute="height" constant="40" id="4X4-Hj-TzA"/>
                     </constraints>
@@ -70,8 +85,8 @@
                     <fontDescription key="fontDescription" type="system" pointSize="14"/>
                     <textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
                 </textView>
-                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="m6t-Me-xFW">
-                    <rect key="frame" x="20" y="802" width="40" height="40"/>
+                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="m6t-Me-xFW" userLabel="Button Config">
+                    <rect key="frame" x="20" y="768" width="40" height="40"/>
                     <color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                     <constraints>
                         <constraint firstAttribute="width" constant="40" id="osz-OV-n7L"/>
@@ -83,8 +98,8 @@
                         <imageReference key="image" image="gearshape.fill" catalog="system" symbolScale="large"/>
                     </state>
                 </button>
-                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="fNr-UI-Smq">
-                    <rect key="frame" x="354" y="802" width="40" height="40"/>
+                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="fNr-UI-Smq" userLabel="Button Send">
+                    <rect key="frame" x="354" y="768" width="40" height="40"/>
                     <color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                     <constraints>
                         <constraint firstAttribute="width" constant="40" id="T6T-5w-Lbw"/>
@@ -96,20 +111,21 @@
             <viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
             <color key="backgroundColor" systemColor="systemBackgroundColor"/>
             <constraints>
-                <constraint firstItem="m6t-Me-xFW" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" constant="20" id="1Wg-qA-SJq"/>
-                <constraint firstItem="fnl-2z-Ty3" firstAttribute="bottom" secondItem="TU8-ei-nsO" secondAttribute="bottom" constant="20" id="2FC-An-lfS"/>
+                <constraint firstItem="fnl-2z-Ty3" firstAttribute="trailing" secondItem="TU8-ei-nsO" secondAttribute="trailing" constant="20" id="3y7-yB-fKn"/>
                 <constraint firstItem="fUG-ls-mSU" firstAttribute="trailing" secondItem="fnl-2z-Ty3" secondAttribute="trailing" id="52V-w7-M51"/>
+                <constraint firstItem="HdB-8N-0l1" firstAttribute="top" secondItem="fnl-2z-Ty3" secondAttribute="top" constant="10" id="6l1-Mv-MwC"/>
                 <constraint firstItem="fnl-2z-Ty3" firstAttribute="bottom" secondItem="fNr-UI-Smq" secondAttribute="bottom" constant="20" id="CSE-Zv-Ou4"/>
                 <constraint firstItem="fUG-ls-mSU" firstAttribute="top" secondItem="fnl-2z-Ty3" secondAttribute="top" id="CYI-Qr-Veh"/>
                 <constraint firstItem="fUG-ls-mSU" firstAttribute="bottom" secondItem="fnl-2z-Ty3" secondAttribute="bottom" id="DGd-5J-aUQ"/>
+                <constraint firstItem="fnl-2z-Ty3" firstAttribute="bottom" secondItem="m6t-Me-xFW" secondAttribute="bottom" constant="20" id="GWK-9A-opG"/>
+                <constraint firstItem="TU8-ei-nsO" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" constant="65" id="KXD-ok-nGH"/>
                 <constraint firstItem="fUG-ls-mSU" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" id="Omw-Qi-oJp"/>
+                <constraint firstItem="m6t-Me-xFW" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" constant="20" id="QKt-Mv-7uC"/>
+                <constraint firstItem="fnl-2z-Ty3" firstAttribute="trailing" secondItem="HdB-8N-0l1" secondAttribute="trailing" constant="10" id="dJr-3l-g1N"/>
                 <constraint firstItem="kDJ-TL-vNL" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" constant="10" id="dWu-IR-Bty"/>
-                <constraint firstAttribute="trailing" secondItem="TU8-ei-nsO" secondAttribute="trailing" constant="20" id="fsX-VB-TEi"/>
-                <constraint firstAttribute="trailing" secondItem="fNr-UI-Smq" secondAttribute="trailing" constant="20" id="iDY-4Q-9JW"/>
-                <constraint firstItem="fnl-2z-Ty3" firstAttribute="bottom" secondItem="m6t-Me-xFW" secondAttribute="bottom" constant="20" id="svH-5g-qh7"/>
+                <constraint firstItem="fnl-2z-Ty3" firstAttribute="bottom" secondItem="TU8-ei-nsO" secondAttribute="bottom" constant="20" id="nbp-vo-svJ"/>
                 <constraint firstItem="kDJ-TL-vNL" firstAttribute="top" secondItem="fnl-2z-Ty3" secondAttribute="top" constant="10" id="wQl-qg-xOE"/>
                 <constraint firstItem="fnl-2z-Ty3" firstAttribute="trailing" secondItem="fNr-UI-Smq" secondAttribute="trailing" constant="20" id="ys4-il-caW"/>
-                <constraint firstItem="TU8-ei-nsO" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" constant="65" id="zXq-IK-dyl"/>
             </constraints>
             <point key="canvasLocation" x="-62.318840579710148" y="99.776785714285708"/>
         </view>
@@ -117,6 +133,7 @@
     <resources>
         <image name="Send-(White)" width="500" height="500"/>
         <image name="gearshape.fill" catalog="system" width="128" height="123"/>
+        <image name="pb_ic_attach_spc_off" width="512" height="512"/>
         <image name="xmark" catalog="system" width="128" height="113"/>
         <systemColor name="systemBackgroundColor">
             <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>

BIN
NexilisLite/NexilisLite/Source/.DS_Store


+ 164 - 45
NexilisLite/NexilisLite/Source/APIS.swift

@@ -1040,7 +1040,7 @@ public class APIS: NSObject {
     public static var uuidCall: UUID?
     public static var fpinCall: String?
     public static func showNotificationNexilis(_ userInfo: [AnyHashable : Any]) {
-        print("MASUK SHOW NOTIFICATION NEXILIS: \(userInfo)")
+//        print("MASUK SHOW NOTIFICATION NEXILIS: \(userInfo)")
         if checkAppStateisBackground() {
 //            Nexilis.sendStateToServer(s: "MASUK SHOW NOTIFICATION NEXILIS")
 //            print("MASUK SHOW NOTIFICATION NEXILIS: \(userInfo)")
@@ -1192,8 +1192,8 @@ public class APIS: NSObject {
         }
     }
     
-    private static func ackAPN(id: String) {
-        DispatchQueue.global(qos: .background).async {
+    static func ackAPN(id: String) {
+        DispatchQueue.global().async {
 //            Nexilis.sendStateToServer(s: "send ack from apn")
             let parameter: [String : Any] = [
                 "pin": User.getMyPin() ?? "",
@@ -1205,11 +1205,12 @@ public class APIS: NSObject {
     }
     
     private static func getMessageById(id: String) {
-        DispatchQueue.global(qos: .background).async {
+        DispatchQueue.global().async {
             let parameter: [String : Any] = [
                 "pin": User.getMyPin() ?? "",
                 "message_id": id
             ]
+//            HttpBackgroundManager().startUpload(parameter: parameter, to: URL(string: Utils.getDomainOpr() + "pull_notification")!, identifier: "pull_notification")
             Utils.postDataWithCookiesAndUserAgent(from: URL(string: Utils.getDomainOpr() + "pull_notification")!, parameter: parameter, isFormData: true) { data, response, error in
                 if let data = data {
                     do {
@@ -1245,6 +1246,9 @@ public class APIS: NSObject {
                     }
                 }
             }
+            DispatchQueue.main.async {
+                UIApplication.shared.applicationIconBadgeNumber = Int(APIS.getTotalCounter())
+            }
 //            Nexilis.sendStateToServer(s: "send ack from apn")
 //            do {
 //                if API.nGetCLXConnState() == 0 {
@@ -1879,11 +1883,16 @@ public class APIS: NSObject {
         Nexilis.destroyAll()
     }
     
+    private static var isCheckingDataForShare = false
     public static func checkDataForShareExtension() {
         DispatchQueue.global().async {
             if let userDefaults = UserDefaults(suiteName: nameGroupShared) {
                 if let value = userDefaults.string(forKey: "sharedItem") {
                     if !value.isEmpty {
+                        if isCheckingDataForShare {
+                            return
+                        }
+                        isCheckingDataForShare = true
                         if let jsonData = value.data(using: .utf8) {
                             do {
                                 if let json = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] {
@@ -1919,76 +1928,187 @@ public class APIS: NSObject {
                                         })
                                     }
                                     if typeShare == typeImage {
+                                        attachmentFlag = "1"
                                         if let appGroupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: nameGroupShared) {
                                             let sharedImageURL = appGroupURL.appendingPathComponent(imageId)
                                             let sharedThumbURL = appGroupURL.appendingPathComponent(thumb)
-                                            let documentDir = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
-                                            if FileManager.default.fileExists(atPath: sharedImageURL.path) {
-                                                let file = documentDir.appendingPathComponent(imageId)
-                                                if !FileManager().fileExists(atPath: file.path) {
-                                                    try? FileManager.default.copyItem(at: sharedImageURL, to: file)
+                                            if Nexilis.checkingAccess(key: "content_inspection") {
+                                                DispatchQueue.main.async {
+                                                    Nexilis.showLoader(text: "Scanning File...".localized())
                                                 }
+                                                let result = sharedImageURL.validateFile()
+                                                DispatchQueue.main.async {
+                                                    Nexilis.hideLoader {
+                                                        if result == 1 {
+                                                            copyData()
+                                                            sendIt()
+                                                        } else {
+                                                            APIS.showWarningFile(type: result)
+                                                            resetPrefs()
+                                                        }
+                                                    }
+                                                }
+                                            } else {
+                                                copyData()
+                                                sendIt()
                                             }
-                                            if FileManager.default.fileExists(atPath: sharedThumbURL.path) {
-                                                let file = documentDir.appendingPathComponent(thumb)
-                                                if !FileManager().fileExists(atPath: file.path) {
-                                                    try? FileManager.default.copyItem(at: sharedThumbURL, to: file)
+                                            func copyData() {
+                                                do {
+                                                    let documentDir = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
+                                                    if FileManager.default.fileExists(atPath: sharedImageURL.path) {
+                                                        let file = documentDir.appendingPathComponent(imageId)
+                                                        if !FileManager().fileExists(atPath: file.path) {
+                                                            try? FileManager.default.copyItem(at: sharedImageURL, to: file)
+                                                        }
+                                                    }
+                                                    if FileManager.default.fileExists(atPath: sharedThumbURL.path) {
+                                                        let file = documentDir.appendingPathComponent(thumb)
+                                                        if !FileManager().fileExists(atPath: file.path) {
+                                                            try? FileManager.default.copyItem(at: sharedThumbURL, to: file)
+                                                        }
+                                                    }
+                                                } catch {
+                                                    
                                                 }
                                             }
                                         }
-                                        attachmentFlag = "1"
                                     } else if typeShare == typeVideo {
+                                        attachmentFlag = "2"
                                         if let appGroupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: nameGroupShared) {
                                             let sharedVideoURL = appGroupURL.appendingPathComponent(videoId)
                                             let sharedThumbURL = appGroupURL.appendingPathComponent(thumb)
-                                            let documentDir = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
-                                            if FileManager.default.fileExists(atPath: sharedVideoURL.path) {
-                                                let file = documentDir.appendingPathComponent(videoId)
-                                                if !FileManager().fileExists(atPath: file.path) {
-                                                    try? FileManager.default.copyItem(at: sharedVideoURL, to: file)
+                                            if Nexilis.checkingAccess(key: "content_inspection") {
+                                                DispatchQueue.main.async {
+                                                    Nexilis.showLoader(text: "Scanning File...".localized())
                                                 }
+                                                let result = sharedVideoURL.validateFile()
+                                                DispatchQueue.main.async {
+                                                    Nexilis.hideLoader {
+                                                        if result == 1 {
+                                                            copyData()
+                                                            sendIt()
+                                                        } else {
+                                                            APIS.showWarningFile(type: result)
+                                                            resetPrefs()
+                                                        }
+                                                    }
+                                                }
+                                            } else {
+                                                copyData()
+                                                sendIt()
                                             }
-                                            if FileManager.default.fileExists(atPath: sharedThumbURL.path) {
-                                                let file = documentDir.appendingPathComponent(thumb)
-                                                if !FileManager().fileExists(atPath: file.path) {
-                                                    try? FileManager.default.copyItem(at: sharedThumbURL, to: file)
+                                            func copyData() {
+                                                do {
+                                                    let documentDir = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
+                                                    if FileManager.default.fileExists(atPath: sharedVideoURL.path) {
+                                                        let file = documentDir.appendingPathComponent(videoId)
+                                                        if !FileManager().fileExists(atPath: file.path) {
+                                                            try? FileManager.default.copyItem(at: sharedVideoURL, to: file)
+                                                        }
+                                                    }
+                                                    if FileManager.default.fileExists(atPath: sharedThumbURL.path) {
+                                                        let file = documentDir.appendingPathComponent(thumb)
+                                                        if !FileManager().fileExists(atPath: file.path) {
+                                                            try? FileManager.default.copyItem(at: sharedThumbURL, to: file)
+                                                        }
+                                                    }
+                                                } catch {
+                                                    
                                                 }
                                             }
                                         }
-                                        attachmentFlag = "2"
                                     } else if typeShare == typeFile {
+                                        attachmentFlag = "6"
                                         if let appGroupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: nameGroupShared) {
                                             renamedFileId = "Nexilis_\(Date().currentTimeMillis())_" + fileId
                                             let sharedFileURL = appGroupURL.appendingPathComponent(fileId)
-                                            let documentDir = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
-                                            if FileManager.default.fileExists(atPath: sharedFileURL.path) {
-                                                let file = documentDir.appendingPathComponent(renamedFileId)
-                                                if !FileManager().fileExists(atPath: file.path) {
-                                                    try? FileManager.default.copyItem(at: sharedFileURL, to: file)
+                                            if Nexilis.checkingAccess(key: "content_inspection") {
+                                                DispatchQueue.main.async {
+                                                    Nexilis.showLoader(text: "Scanning File...".localized())
+                                                }
+                                                let result = sharedFileURL.validateFile()
+                                                DispatchQueue.main.async {
+                                                    Nexilis.hideLoader {
+                                                        if result == 1 {
+                                                            copyData()
+                                                            sendIt()
+                                                        } else {
+                                                            APIS.showWarningFile(type: result)
+                                                            resetPrefs()
+                                                        }
+                                                    }
+                                                }
+                                            } else {
+                                                copyData()
+                                                sendIt()
+                                            }
+                                            func copyData() {
+                                                do {
+                                                    let documentDir = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
+                                                    if FileManager.default.fileExists(atPath: sharedFileURL.path) {
+                                                        let file = documentDir.appendingPathComponent(renamedFileId)
+                                                        if !FileManager().fileExists(atPath: file.path) {
+                                                            try? FileManager.default.copyItem(at: sharedFileURL, to: file)
+                                                        }
+                                                    }
+                                                    data = "\(fileId)|\(data)"
+                                                } catch {
+                                                    
                                                 }
                                             }
-                                            data = "\(fileId)|\(data)"
                                         }
-                                        attachmentFlag = "6"
                                     } else if typeShare == typeAudio {
+                                        attachmentFlag = "5"
                                         if let appGroupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: nameGroupShared) {
                                             renamedAudioId = "Nexilis_\(Date().currentTimeMillis())_" + audioId
                                             let sharedFileURL = appGroupURL.appendingPathComponent(audioId)
-                                            let documentDir = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
-                                            if FileManager.default.fileExists(atPath: sharedFileURL.path) {
-                                                let file = documentDir.appendingPathComponent(renamedAudioId)
-                                                if !FileManager().fileExists(atPath: file.path) {
-                                                    try? FileManager.default.copyItem(at: sharedFileURL, to: file)
+                                            if Nexilis.checkingAccess(key: "content_inspection") {
+                                                DispatchQueue.main.async {
+                                                    Nexilis.showLoader(text: "Scanning File...".localized())
+                                                }
+                                                let result = sharedFileURL.validateFile()
+                                                DispatchQueue.main.async {
+                                                    Nexilis.hideLoader {
+                                                        if result == 1 {
+                                                            copyData()
+                                                            sendIt()
+                                                        } else {
+                                                            APIS.showWarningFile(type: result)
+                                                            resetPrefs()
+                                                        }
+                                                    }
+                                                }
+                                            } else {
+                                                copyData()
+                                                sendIt()
+                                            }
+                                            func copyData() {
+                                                do {
+                                                    let documentDir = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
+                                                    if FileManager.default.fileExists(atPath: sharedFileURL.path) {
+                                                        let file = documentDir.appendingPathComponent(renamedAudioId)
+                                                        if !FileManager().fileExists(atPath: file.path) {
+                                                            try? FileManager.default.copyItem(at: sharedFileURL, to: file)
+                                                        }
+                                                    }
+                                                    data = "\(audioId)|\(data)"
+                                                } catch {
+                                                    
                                                 }
                                             }
-                                            data = "\(audioId)|\(data)"
                                         }
-                                        attachmentFlag = "5"
                                     }
-                                    message = CoreMessage_TMessageBank.sendMessage(l_pin: groupId.isEmpty ? idContact : groupId, message_scope_id: scopeId, status: scopeId == "3" ? "1" : "2", message_text: data, credential: "0", attachment_flag: attachmentFlag, ex_blog_id: "", message_large_text: "", ex_format: "", image_id: imageId, audio_id: renamedAudioId, video_id: videoId, file_id: renamedFileId, thumb_id: thumb, reff_id: "", read_receipts: "4", chat_id: chatId, is_call_center: "0", call_center_id: "", opposite_pin: scopeId == "3" ? (User.getMyPin() ?? "") : idContact, gif_id: "", isForwarded: "0", isSecret: "0")
-                                    Nexilis.addQueueMessage(message: message)
-                                    userDefaults.set("", forKey: "sharedItem")
-                                    userDefaults.synchronize()
+                                    func sendIt() {
+                                        message = CoreMessage_TMessageBank.sendMessage(l_pin: groupId.isEmpty ? idContact : groupId, message_scope_id: scopeId, status: scopeId == "3" ? "1" : "2", message_text: data, credential: "0", attachment_flag: attachmentFlag, ex_blog_id: "", message_large_text: "", ex_format: "", image_id: imageId, audio_id: renamedAudioId, video_id: videoId, file_id: renamedFileId, thumb_id: thumb, reff_id: "", read_receipts: "4", chat_id: chatId, is_call_center: "0", call_center_id: "", opposite_pin: scopeId == "3" ? (User.getMyPin() ?? "") : idContact, gif_id: "", isForwarded: "0", isSecret: "0", specFile: "")
+                                        Nexilis.addQueueMessage(message: message)
+                                        resetPrefs()
+                                        NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
+                                    }
+                                    func resetPrefs() {
+                                        userDefaults.set("", forKey: "sharedItem")
+                                        userDefaults.synchronize()
+                                        isCheckingDataForShare = false
+                                    }
                                 }
                             } catch {
                                 print("Error parsing JSON: \(error)")
@@ -1997,9 +2117,7 @@ public class APIS: NSObject {
                     }
                 }
             }
-            DispatchQueue.global().asyncAfter(deadline: .now() + 0.5, execute: {
-                NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
-            })
+            NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
         }
     }
     
@@ -2206,6 +2324,7 @@ extension UINavigationController {
         self.modalPresentationStyle = .fullScreen
         self.navigationBar.tintColor = .white
         self.navigationBar.barTintColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
+        self.navigationBar.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
         self.navigationBar.isTranslucent = false
         self.navigationBar.overrideUserInterfaceStyle = .dark
         self.navigationBar.barStyle = .black

+ 2 - 1
NexilisLite/NexilisLite/Source/CoreMessage_TMessageBank.swift

@@ -141,7 +141,7 @@ public class CoreMessage_TMessageBank {
         return tmessage
     }
     
-    public static func sendMessage(message_id: String = "", l_pin: String, message_scope_id: String, status: String, message_text: String, credential: String, attachment_flag: String, ex_blog_id: String, message_large_text: String, ex_format: String, image_id: String, audio_id: String, video_id: String, file_id: String, thumb_id: String, reff_id: String, read_receipts: String, chat_id: String, is_call_center: String, call_center_id: String, opposite_pin: String, gif_id: String = "", isForwarded: String = "", isSecret: String = "") -> TMessage {
+    public static func sendMessage(message_id: String = "", l_pin: String, message_scope_id: String, status: String, message_text: String, credential: String, attachment_flag: String, ex_blog_id: String, message_large_text: String, ex_format: String, image_id: String, audio_id: String, video_id: String, file_id: String, thumb_id: String, reff_id: String, read_receipts: String, chat_id: String, is_call_center: String, call_center_id: String, opposite_pin: String, gif_id: String = "", isForwarded: String = "", isSecret: String = "", specFile: String) -> TMessage {
         let me = User.getMyPin()!
         let tmessage = TMessage()
         tmessage.mCode = CoreMessage_TMessageCode.SEND_CHAT
@@ -197,6 +197,7 @@ public class CoreMessage_TMessageBank {
         }
         tmessage.mBodies[CoreMessage_TMessageKey.IS_FORWARDED_MESSAGE] = isForwarded
         tmessage.mBodies[CoreMessage_TMessageKey.IS_SECRET] = isSecret
+        tmessage.mBodies[CoreMessage_TMessageKey.ATTACHMENT_SPECIALITY] = specFile
         return tmessage
     }
     

+ 1 - 0
NexilisLite/NexilisLite/Source/CoreMessage_TMessageKey.swift

@@ -401,6 +401,7 @@ public class CoreMessage_TMessageKey {
     public static let COUNT_POST = "A170"
     public static let COUNT_FOLLOWER = "A171"
     public static let COUNT_FOLLOWING = "A172"
+    public static let ATTACHMENT_SPECIALITY = "A180";
     public static let NEW_RACI_R = "new_r"
     public static let NEW_RACI_A = "new_a"
     public static let NEW_RACI_C = "new_c"

+ 5 - 1
NexilisLite/NexilisLite/Source/Database.swift

@@ -100,6 +100,8 @@ public class Database {
                 try createDatabase(fmdb: fmdb)
                 addColumnIfNeeded(database: fmdb, tableName: "MESSAGE_SUMMARY", columnName: "pinned", columnType: "INTEGER", defaultValue: "0")
                 addColumnIfNeeded(database: fmdb, tableName: "MESSAGE_SUMMARY", columnName: "archived", columnType: "INTEGER", defaultValue: "0")
+                addColumnIfNeeded(database: fmdb, tableName: "MESSAGE", columnName: "is_pinned", columnType: "TEXT", defaultValue: "0")
+                addColumnIfNeeded(database: fmdb, tableName: "MESSAGE", columnName: "attachment_speciality", columnType: "TEXT", defaultValue: "")
                 result = 1
 //                    print("Create Done")
             } catch {
@@ -309,7 +311,9 @@ public class Database {
                                 "'last_edited' INTEGER DEFAULT 0," +
                                 "'is_secret' INTEGER DEFAULT 0," +
                                 "'is_deleted_retention' INTEGER DEFAULT 0," +
-                                "'is_forwarded_message' INTEGER DEFAULT 0" +
+                                "'is_forwarded_message' INTEGER DEFAULT 0," +
+                                "'is_pinned' INTEGER DEFAULT 0," +
+                                "'attachment_speciality' TEXT" +
                                 ")", values: nil)
         
         try fmdb.executeUpdate("CREATE INDEX IF NOT EXISTS index_m_opposite on MESSAGE (opposite_pin, chat_id)", values: nil)

+ 19 - 12
NexilisLite/NexilisLite/Source/Extension.swift

@@ -572,27 +572,25 @@ extension URL {
         let hexString = data.prefix(4).map { String(format: "%02X", $0) }.joined()
         let extUploadedFile = self.absoluteString.split(separator: ".").last ?? ""
         let dataPrefs = Utils.getWhitelistFileExt()
+//        print("HOHOHO: \(extUploadedFile) <><>> \(hexString) <><><> \(dataPrefs)")
         guard !dataPrefs.isEmpty,
               let jsonData = dataPrefs.data(using: .utf8) else {
             return nil
         }
         do {
             if let jsonArray = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [[String: Any]] {
-                guard let _ = jsonArray.firstIndex(where: { $0["ext"] as? String ?? "" == extUploadedFile }) else {
+                guard let _ = jsonArray.firstIndex(where: { $0["ext"] as? String ?? "" == extUploadedFile.lowercased() }) else {
                     return nil
                 }
-                if let idxRealFile = jsonArray.firstIndex(where: {
-                    guard let magicArray = $0["magic"] as? [String] else { return false }
-                    return magicArray.contains(where: { magicItem in
-                        if magicItem.contains("~") {
-                            return magicItem.split(separator: "~").contains { $0 == hexString }
-                        } else {
-                            return magicItem == hexString
-                        }
-                    })
-                }) {
+                
+                if let idxRealFile = jsonArray.firstIndex(where: { $0["ext"] as? String == extUploadedFile.lowercased()} ) {
                     let jsonRealFile = jsonArray[idxRealFile]
-                    return FileTypeSignature(magic: jsonRealFile["magic"] as? [String] ?? [], extensions: jsonRealFile["ext"] as? String ?? "")
+                    let magic = jsonRealFile["magic"] as! [String]
+                    if magic.contains(hexString) || magic.contains(where: { $0.components(separatedBy: "~").contains(hexString) }) {
+                        return FileTypeSignature(magic: jsonRealFile["magic"] as? [String] ?? [], extensions: jsonRealFile["ext"] as? String ?? "")
+                    } else if isMP4File(data) {
+                        return FileTypeSignature(magic: jsonRealFile["magic"] as? [String] ?? [], extensions: jsonRealFile["ext"] as? String ?? "")
+                    }
                 }
             }
         } catch {
@@ -602,6 +600,15 @@ extension URL {
         return nil
     }
     
+    func isMP4File(_ data: Data) -> Bool {
+        guard data.count >= 12 else { return false }
+        
+        // Read bytes 4-7 (ftyp)
+        let ftyp = String(data: data.subdata(in: 4..<8), encoding: .ascii)
+        
+        return ftyp == "ftyp"
+    }
+    
     func isEncryptedPDF(data: Data) -> Bool {
         guard let document = PDFDocument(data: data) else {
             return false

+ 5 - 4
NexilisLite/NexilisLite/Source/Nexilis.swift

@@ -19,7 +19,7 @@ import CryptoKit
 import WebKit
 
 public class Nexilis: NSObject {
-    public static var cpaasVersion = "5.0.43"
+    public static var cpaasVersion = "5.0.44"
     public static var sAPIKey = ""
     
     public static var ADDRESS = ""
@@ -525,7 +525,7 @@ public class Nexilis: NSObject {
             return
         }
         isGettingFeatureAccess = true
-        DispatchQueue.global(qos: .background).async {
+        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 {
@@ -826,7 +826,7 @@ public class Nexilis: NSObject {
 //    }
     
     public static func apiSendChat(destination: String, message: String, isGroup: Bool, thumbnailName: String = "", imageName: String = "", videoName: String = "", fileName: String = "", audioName: String = "", replyMessageId : String = "") -> String {
-        let message = CoreMessage_TMessageBank.sendMessage(l_pin: destination, message_scope_id: isGroup ? MessageScope.GROUP : MessageScope.WHISPER, status: "3", message_text: message, credential: "", attachment_flag: !imageName.isEmpty ? "1" : !videoName.isEmpty ? "2" : !audioName.isEmpty ? "5" : !fileName.isEmpty ? "6" : "0", ex_blog_id: "", message_large_text: "", ex_format: "", image_id: imageName, audio_id: audioName, video_id: videoName, file_id: fileName, thumb_id: thumbnailName, reff_id: replyMessageId, read_receipts: "4", chat_id: "", is_call_center: "0", call_center_id: "", opposite_pin: User.getMyPin() ?? "")
+        let message = CoreMessage_TMessageBank.sendMessage(l_pin: destination, message_scope_id: isGroup ? MessageScope.GROUP : MessageScope.WHISPER, status: "3", message_text: message, credential: "", attachment_flag: !imageName.isEmpty ? "1" : !videoName.isEmpty ? "2" : !audioName.isEmpty ? "5" : !fileName.isEmpty ? "6" : "0", ex_blog_id: "", message_large_text: "", ex_format: "", image_id: imageName, audio_id: audioName, video_id: videoName, file_id: fileName, thumb_id: thumbnailName, reff_id: replyMessageId, read_receipts: "4", chat_id: "", is_call_center: "0", call_center_id: "", opposite_pin: User.getMyPin() ?? "", specFile: "")
         addQueueMessage(message: message)
         return message.getBody(key: CoreMessage_TMessageKey.MESSAGE_ID)
     }
@@ -1570,7 +1570,8 @@ public class Nexilis: NSObject {
                         "last_edited" : last_edited,
                         "is_secret" : is_secret,
                         "is_deleted_retention" : is_delete_retention,
-                        "is_forwarded_message" : is_forwarded_message
+                        "is_forwarded_message" : is_forwarded_message,
+                        "attachment_speciality" : message.getBody(key: CoreMessage_TMessageKey.ATTACHMENT_SPECIALITY, default_value:  "")
                     ], replace: true)
                 } catch {
                     print("ERROR: \(error)")

+ 311 - 0
NexilisLite/NexilisLite/Source/Utils.swift

@@ -13,6 +13,7 @@ import nuSDKService
 import CoreLocation
 import CryptoKit
 import LocalAuthentication
+import AVFoundation
 //import var CommonCrypto.CC_MD5_DIGEST_LENGTH
 //import func CommonCrypto.CC_MD5
 //import typealias CommonCrypto.CC_LONG
@@ -2892,3 +2893,313 @@ public class MessageScope {
     public static let CHANNEL = "33";
 }
 
+class MediaViewerViewController: UIViewController, UIGestureRecognizerDelegate, UIScrollViewDelegate {
+    
+    enum MediaType {
+        case image(UIImage)
+        case gif(UIImage)
+        case video(URL)
+    }
+
+    var media: MediaType!
+
+    public let backgroundView = UIView()
+    private let scrollView = UIScrollView()
+    private let imageView = UIImageView()
+    private var statusBarBackgroundView: UIView!
+    private var player: AVPlayer?
+    private var playerLayer: AVPlayerLayer?
+    private let playPauseButton = UIButton(type: .custom)
+    private var isVideoPlaying = false
+
+    var isNavigationBarHidden = false {
+        didSet { setNeedsStatusBarAppearanceUpdate() }
+    }
+
+    override var prefersStatusBarHidden: Bool {
+        return isNavigationBarHidden
+    }
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+
+        view.backgroundColor = .clear
+
+        edgesForExtendedLayout = .all
+        extendedLayoutIncludesOpaqueBars = true
+        navigationController?.navigationBar.isTranslucent = true
+
+        // Background view
+        backgroundView.backgroundColor = .white
+        backgroundView.alpha = 0
+        backgroundView.frame = view.bounds
+        view.addSubview(backgroundView)
+
+        // ScrollView for zooming
+        scrollView.frame = view.bounds
+        scrollView.delegate = self
+        scrollView.minimumZoomScale = 1.0
+        scrollView.maximumZoomScale = 3.0
+        scrollView.showsVerticalScrollIndicator = false
+        scrollView.showsHorizontalScrollIndicator = false
+        scrollView.bouncesZoom = true
+        view.addSubview(scrollView)
+
+        // Add imageView to scrollView
+        imageView.frame = scrollView.bounds
+        imageView.contentMode = .scaleAspectFit
+        scrollView.addSubview(imageView)
+
+        configureMedia()
+
+        // Tap gesture to toggle navigation bar
+        let tap = UITapGestureRecognizer(target: self, action: #selector(toggleNavigationBar))
+        tap.numberOfTapsRequired = 1
+        view.addGestureRecognizer(tap)
+
+        // Pan gesture for swipe-to-dismiss
+        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
+        panGesture.delegate = self
+        view.addGestureRecognizer(panGesture)
+
+        // Status bar background view
+        let window = UIApplication.shared.windows.first
+        let statusBarHeight = window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 44
+
+        statusBarBackgroundView = UIView(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: statusBarHeight))
+        statusBarBackgroundView.backgroundColor = .mainColor
+        statusBarBackgroundView.autoresizingMask = [.flexibleWidth, .flexibleBottomMargin]
+        view.addSubview(statusBarBackgroundView)
+    }
+
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+        navigationController?.setNavigationBarHidden(false, animated: false)
+        isNavigationBarHidden = false
+    }
+
+    func animateBackgroundIn() {
+        UIView.animate(withDuration: 0.25) {
+            self.backgroundView.alpha = 1
+        }
+    }
+
+    private func configureMedia() {
+        switch media! {
+        case .image(let img):
+            imageView.image = img
+
+        case .gif(let img):
+            imageView.image = img
+            imageView.animationRepeatCount = 0
+            imageView.startAnimating()
+
+        case .video(let url):
+            setupVideo(url: url)
+        }
+    }
+
+    private func setupVideo(url: URL) {
+        player = AVPlayer(url: url)
+        playerLayer = AVPlayerLayer(player: player)
+        playerLayer?.frame = scrollView.bounds
+        playerLayer?.videoGravity = .resizeAspect
+        scrollView.layer.addSublayer(playerLayer!)
+        
+        // Observe when video finished playing
+        NotificationCenter.default.addObserver(self, selector: #selector(videoDidFinish), name: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem)
+
+        // Add play/pause button
+        playPauseButton.setImage(UIImage(systemName: "play.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .bold, scale: .default)), for: .normal)
+        playPauseButton.tintColor = .white
+        playPauseButton.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
+        playPauseButton.backgroundColor = .black.withAlphaComponent(0.3)
+        playPauseButton.center = view.center
+        playPauseButton.addTarget(self, action: #selector(togglePlayPause), for: .touchUpInside)
+        view.addSubview(playPauseButton)
+        playPauseButton.circle()
+        
+        togglePlayPause()
+    }
+    
+    @objc private func videoDidFinish() {
+        isVideoPlaying = false
+        playPauseButton.setImage(UIImage(systemName: "play.fill"), for: .normal)
+    }
+
+    @objc private func togglePlayPause() {
+        guard let player = player else { return }
+
+        if isVideoPlaying {
+            player.pause()
+            playPauseButton.setImage(UIImage(systemName: "play.fill"), for: .normal)
+        } else {
+            if let currentItem = player.currentItem,
+               currentItem.currentTime() >= currentItem.duration {
+                player.seek(to: .zero)
+            }
+            player.play()
+            playPauseButton.setImage(UIImage(systemName: "pause.fill"), for: .normal)
+            DispatchQueue.global().async {
+                while self.statusBarBackgroundView == nil {
+                    Thread.sleep(forTimeInterval: 0.25)
+                }
+                DispatchQueue.main.async {
+                    self.toggleNavigationBar()
+                }
+            }
+        }
+        isVideoPlaying.toggle()
+    }
+
+    @objc private func toggleNavigationBar() {
+        guard let navController = navigationController else { return }
+
+        isNavigationBarHidden.toggle()
+
+        UIView.animate(withDuration: 0.25) {
+            navController.setNavigationBarHidden(self.isNavigationBarHidden, animated: true)
+            self.statusBarBackgroundView.alpha = self.isNavigationBarHidden ? 0 : 1
+            self.playPauseButton.alpha = self.isNavigationBarHidden ? 0 : 1
+        }
+    }
+
+    @objc private func handlePan(_ gesture: UIPanGestureRecognizer) {
+        guard scrollView.zoomScale == 1.0 else { return }
+
+        let translation = gesture.translation(in: view)
+        let velocity = gesture.velocity(in: view)
+
+        switch gesture.state {
+        case .changed:
+            let transform = CGAffineTransform(translationX: translation.x, y: translation.y)
+            scrollView.transform = transform
+            
+            // Calculate percentage based on distance from center
+            let distance = hypot(translation.x, translation.y)
+            let maxDistance = view.bounds.height / 2.0
+            let progress = min(distance / maxDistance, 1.0)
+            self.backgroundView.alpha = 1.0 - progress
+
+        case .ended, .cancelled:
+            let distance = hypot(translation.x, translation.y)
+            let threshold: CGFloat = 120
+
+            if distance > threshold || abs(velocity.y) > 500 || abs(velocity.x) > 500 {
+                // Dismiss if far enough or fast swipe
+                dismiss(animated: true, completion: nil)
+            } else {
+                // Return to center if not far enough
+                UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0.8, options: [], animations: {
+                    self.scrollView.transform = .identity
+                    self.backgroundView.alpha = 1.0
+                }, completion: nil)
+            }
+        default:
+            break
+        }
+    }
+
+    // MARK: - UIScrollViewDelegate
+
+    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
+        return imageView
+    }
+
+    func scrollViewDidZoom(_ scrollView: UIScrollView) {
+        let imageViewSize = imageView.frame.size
+        let scrollViewSize = scrollView.bounds.size
+        let verticalPadding = imageViewSize.height < scrollViewSize.height ? (scrollViewSize.height - imageViewSize.height) / 2 : 0
+        let horizontalPadding = imageViewSize.width < scrollViewSize.width ? (scrollViewSize.width - imageViewSize.width) / 2 : 0
+        scrollView.contentInset = UIEdgeInsets(top: verticalPadding, left: horizontalPadding, bottom: verticalPadding, right: horizontalPadding)
+    }
+}
+
+class ZoomAnimator: NSObject, UIViewControllerAnimatedTransitioning {
+    var isPresenting = true
+    var originImageView: UIImageView?
+
+    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
+        return 0.45
+    }
+
+    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
+        guard let fromVC = transitionContext.viewController(forKey: .from),
+              let toVC = transitionContext.viewController(forKey: .to),
+              let originImageView = originImageView else {
+            transitionContext.completeTransition(false)
+            return
+        }
+
+        let container = transitionContext.containerView
+        let imageViewSnapshot = UIImageView(image: originImageView.image)
+        imageViewSnapshot.contentMode = .scaleAspectFit
+        imageViewSnapshot.clipsToBounds = true
+        imageViewSnapshot.frame = container.convert(originImageView.bounds, from: originImageView)
+
+        if isPresenting {
+            toVC.view.alpha = 0
+            container.addSubview(toVC.view)
+            container.addSubview(imageViewSnapshot)
+
+            let finalFrame = toVC.view.frame
+
+            UIView.animate(withDuration: transitionDuration(using: transitionContext),
+                           delay: 0,
+                           usingSpringWithDamping: 0.85,
+                           initialSpringVelocity: 0.6,
+                           options: .curveEaseOut, animations: {
+
+                imageViewSnapshot.frame = finalFrame
+                toVC.view.alpha = 1
+
+            }) { _ in
+                imageViewSnapshot.removeFromSuperview()
+                transitionContext.completeTransition(true)
+            }
+
+        } else {
+            let navVC = fromVC as! UINavigationController
+            let fromImageVC = navVC.viewControllers.first as! MediaViewerViewController
+            let finalFrame = container.convert(originImageView.bounds, from: originImageView)
+
+            container.addSubview(imageViewSnapshot)
+            fromImageVC.view.alpha = 0
+            fromImageVC.backgroundView.alpha = 0 // fade background
+
+            UIView.animate(withDuration: transitionDuration(using: transitionContext),
+                           delay: 0,
+                           usingSpringWithDamping: 0.85,
+                           initialSpringVelocity: 0.6,
+                           options: .curveEaseOut, animations: {
+
+                imageViewSnapshot.frame = finalFrame
+
+            }) { _ in
+                imageViewSnapshot.removeFromSuperview()
+                transitionContext.completeTransition(true)
+            }
+        }
+    }
+}
+
+class ZoomTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
+    var originImageView: UIImageView?
+
+    func animationController(forPresented presented: UIViewController,
+                             presenting: UIViewController, source: UIViewController)
+        -> UIViewControllerAnimatedTransitioning? {
+            let animator = ZoomAnimator()
+            animator.isPresenting = true
+            animator.originImageView = originImageView
+            return animator
+    }
+
+    func animationController(forDismissed dismissed: UIViewController)
+        -> UIViewControllerAnimatedTransitioning? {
+            let animator = ZoomAnimator()
+            animator.isPresenting = false
+            animator.originImageView = originImageView
+            return animator
+    }
+}

BIN
NexilisLite/NexilisLite/Source/View/.DS_Store


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

@@ -319,7 +319,7 @@ public class ChatGPTBotView: UIViewController, UIGestureRecognizerDelegate {
         let idMe = User.getMyPin() as String?
         let opposite_pin = idMe ?? ""
         sendTyping(l_pin: l_pin, isTyping: true)
-        let message = CoreMessage_TMessageBank.sendMessage(l_pin: l_pin, message_scope_id: message_scope_id, status: status, message_text: message_text, credential: credential, attachment_flag: attachment_flag, ex_blog_id: ex_blog_id, message_large_text: message_large_text, ex_format: ex_format, image_id: image_id, audio_id: audio_id, video_id: video_id, file_id: file_id, thumb_id: thumb_id, reff_id: reff_id, read_receipts: read_receipts, chat_id: chat_id, is_call_center: is_call_center, call_center_id: call_center_id, opposite_pin: opposite_pin)
+        let message = CoreMessage_TMessageBank.sendMessage(l_pin: l_pin, message_scope_id: message_scope_id, status: status, message_text: message_text, credential: credential, attachment_flag: attachment_flag, ex_blog_id: ex_blog_id, message_large_text: message_large_text, ex_format: ex_format, image_id: image_id, audio_id: audio_id, video_id: video_id, file_id: file_id, thumb_id: thumb_id, reff_id: reff_id, read_receipts: read_receipts, chat_id: chat_id, is_call_center: is_call_center, call_center_id: call_center_id, opposite_pin: opposite_pin, specFile: "")
         Nexilis.saveMessage(message: message)
         let messageId = String(message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID]!)
         var row: [String: Any?] = [:]

+ 376 - 195
NexilisLite/NexilisLite/Source/View/Chat/EditorGroup.swift

@@ -123,6 +123,11 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
     
     var downloadList: [String: IndexPath] = [:]
     
+    var transitioningDelegateRef: ZoomTransitioningDelegate?
+    var buttonSpec = UIButton(type: .custom)
+    var tableViewConfigFile: UITableView!
+    var specFileString = ""
+    
     var tableMentionEdit = UITableView()
     var heightTableEditMention: NSLayoutConstraint!
     
@@ -640,7 +645,7 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
     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, last_edited, gif_id, is_forwarded_message 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, gif_id, is_forwarded_message, attachment_speciality 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 ?? "" != "") {
@@ -673,6 +678,7 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
                         row[TypeDataMessage.last_edit] = cursorData.longLongInt(forColumnIndex: 20)
                         row[TypeDataMessage.gif_id] = cursorData.string(forColumnIndex: 21)
                         row[TypeDataMessage.is_forwarded] = Int(cursorData.int(forColumnIndex: 22))
+                        row[TypeDataMessage.spec_file] = cursorData.string(forColumnIndex: 23)
                         row["isSelected"] = false
                         if row["credential"] != nil && row["credential"]  as? String ?? "" == "1" {
                             let idMe = User.getMyPin()!
@@ -1124,6 +1130,7 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
                     row["lock"] = ""
                     row["is_stared"] = "0"
                     row[TypeDataMessage.is_forwarded] = Int(chatData[CoreMessage_TMessageKey.IS_FORWARDED_MESSAGE] ?? "0")
+                    row[TypeDataMessage.spec_file] = chatData[CoreMessage_TMessageKey.ATTACHMENT_SPECIALITY]
                     row["isSelected"] = false
                     if !self.dataDates.contains("Today".localized()){
                         self.dataDates.append("Today".localized())
@@ -1470,7 +1477,7 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
             case "image":
                 var config = PHPickerConfiguration()
                 config.filter = .images
-                config.preferredAssetRepresentationMode = .current
+                config.preferredAssetRepresentationMode = .automatic
                 let picker = PHPickerViewController(configuration: config)
                 picker.delegate = self
                 if UIBarButtonItem.appearance().titleTextAttributes(for: .normal) != nil {
@@ -1484,7 +1491,7 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
             case "video":
                 var config = PHPickerConfiguration()
                 config.filter = .videos
-                config.preferredAssetRepresentationMode = .current
+                config.preferredAssetRepresentationMode = .automatic
                 let picker = PHPickerViewController(configuration: config)
                 picker.delegate = self
                 if UIBarButtonItem.appearance().titleTextAttributes(for: .normal) != nil {
@@ -1868,7 +1875,7 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
                 }
             }
         }
-        let message = CoreMessage_TMessageBank.sendMessage(l_pin: dataGroup["group_id"]  as? String ?? "", message_scope_id: message_scope_id, status: status, message_text: message_text, credential: credential, attachment_flag: attachment_flag, ex_blog_id: ex_blog_id, message_large_text: message_large_text, ex_format: ex_format, image_id: image_id, audio_id: audio_id, video_id: video_id, file_id: file_id, thumb_id: thumb_id, reff_id: reff_id, read_receipts: read_receipts, chat_id: dataTopic["chat_id"]  as? String ?? "", is_call_center: is_call_center, call_center_id: call_center_id, opposite_pin: opposite_pin, gif_id: gif_id, isForwarded: "\(is_forwarded)")
+        let message = CoreMessage_TMessageBank.sendMessage(l_pin: dataGroup["group_id"]  as? String ?? "", message_scope_id: message_scope_id, status: status, message_text: message_text, credential: credential, attachment_flag: attachment_flag, ex_blog_id: ex_blog_id, message_large_text: message_large_text, ex_format: ex_format, image_id: image_id, audio_id: audio_id, video_id: video_id, file_id: file_id, thumb_id: thumb_id, reff_id: reff_id, read_receipts: read_receipts, chat_id: dataTopic["chat_id"]  as? String ?? "", is_call_center: is_call_center, call_center_id: call_center_id, opposite_pin: opposite_pin, gif_id: gif_id, isForwarded: "\(is_forwarded)", specFile: specFileString)
         Nexilis.addQueueMessage(message: message)
         let messageId = String(message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID]!)
         if credential == "1" {
@@ -1901,6 +1908,8 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
         row[TypeDataMessage.is_call_center] = is_call_center
         row[TypeDataMessage.call_center_id] = call_center_id
         row[TypeDataMessage.opposite_pin] = opposite_pin
+        row[TypeDataMessage.spec_file] = specFileString
+        specFileString = ""
         if !dataDates.contains("Today".localized()){
             dataDates.append("Today".localized())
             tableChatView.insertSections(IndexSet(integer: dataDates.count - 1), with: .fade)
@@ -2354,7 +2363,7 @@ extension EditorGroup: ImageVideoPickerDelegate, PreviewAttachmentImageVideoDele
         }
         if result.itemProvider.hasItemConformingToTypeIdentifier("com.compuserve.gif") {
             picker.dismiss(animated: true, completion: {
-                Nexilis.showLoader()
+                Nexilis.showLoader(text: "Preparing...".localized())
                 result.itemProvider.loadDataRepresentation(forTypeIdentifier: "com.compuserve.gif") { data, error in
                     if let error = error {
                         print("Error loading GIF: \(error.localizedDescription)")
@@ -2383,11 +2392,10 @@ extension EditorGroup: ImageVideoPickerDelegate, PreviewAttachmentImageVideoDele
             })
         } else if result.itemProvider.hasItemConformingToTypeIdentifier("public.image") {
             picker.dismiss(animated: true, completion: {
-                Nexilis.showLoader()
-                result.itemProvider.loadFileRepresentation(forTypeIdentifier: "public.image") { url, error in
-                    if let url = url {
+                Nexilis.showLoader(text: "Preparing...".localized())
+                result.itemProvider.loadDataRepresentation(forTypeIdentifier: "public.image") { data, error in
+                    if let data = data {
                         do {
-                            let data = try Data(contentsOf: url)
                             DispatchQueue.main.async {
                                 Nexilis.hideLoader {
                                     let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
@@ -2413,17 +2421,21 @@ extension EditorGroup: ImageVideoPickerDelegate, PreviewAttachmentImageVideoDele
             })
         } else if result.itemProvider.hasItemConformingToTypeIdentifier("public.movie") {
             picker.dismiss(animated: true, completion: {
-                Nexilis.showLoader()
-                result.itemProvider.loadFileRepresentation(forTypeIdentifier: "public.movie") { tempURL, error in
-                    if let tempURL = tempURL {
+                Nexilis.showLoader(text: "Preparing...".localized())
+                result.itemProvider.loadFileRepresentation(forTypeIdentifier: "public.movie") { url, error in
+                    if let url = url {
                         let fileManager = FileManager.default
                         let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
-                        let destinationURL = documentsDirectory.appendingPathComponent(tempURL.lastPathComponent)
+                        var nameFile = url.lastPathComponent
+                        if nameFile.contains("&uuid"){
+                            nameFile = UUID().uuidString + ".mov"
+                        }
+                        let destinationURL = documentsDirectory.appendingPathComponent(nameFile)
                         do {
                             if fileManager.fileExists(atPath: destinationURL.path) {
                                 try fileManager.removeItem(at: destinationURL)
                             }
-                            try fileManager.copyItem(at: tempURL, to: destinationURL)
+                            try fileManager.copyItem(at: url, to: destinationURL)
                             DispatchQueue.main.async {
                                 Nexilis.hideLoader {
                                     let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
@@ -2447,7 +2459,8 @@ extension EditorGroup: ImageVideoPickerDelegate, PreviewAttachmentImageVideoDele
         }
     }
     
-    func sendChatFromPreviewImage(message_text: String, attachment_flag: String, image_id: String, video_id: String, thumb_id: String, gif_id: String, viewController: UIViewController) {
+    func sendChatFromPreviewImage(message_text: String, attachment_flag: String, image_id: String, video_id: String, thumb_id: String, gif_id: String, viewController: UIViewController, specFile: String) {
+        specFileString = specFile
         sendChat(message_text: message_text, attachment_flag: attachment_flag, image_id: image_id, video_id: video_id, thumb_id: thumb_id, viewController: viewController, gif_id: gif_id)
     }
 }
@@ -2457,21 +2470,105 @@ extension EditorGroup: UIDocumentPickerDelegate, DocumentPickerDelegate, QLPrevi
         if (document != nil) {
             self.previewItem = (document as! [URL])[0] as NSURL
             let previewController = QLPreviewController()
-            let navController = CustomNavigationController(rootViewController: previewController)
-            navController.navigationBar.tintColor = .black
-            let cancelButtonAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: UIColor.black, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
-            UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes, for: .normal)
-            let leftBarButton = navigationQLPreviewDocument(title: "Cancel".localized(), style: .plain, target: self, action: #selector(cancelDocumentPreview))
-            let rightBarButton = navigationQLPreviewDocument(title: "Send".localized(), style: .done, target: self, action: #selector(sendDocument))
-            leftBarButton.navigation = navController
-            rightBarButton.navigation = navController
-            previewController.navigationItem.leftBarButtonItem = leftBarButton
-            previewController.navigationItem.rightBarButtonItem = rightBarButton
             previewController.dataSource = self
-            previewController.modalPresentationStyle = .pageSheet
+            let vcHandleFile = UIViewController()
+            let nc = UINavigationController(rootViewController: vcHandleFile)
+            let attributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
+            let navBarAppearance = UINavigationBarAppearance()
+            nc.defaultStyle()
+            nc.modalPresentationStyle = .pageSheet
+            navBarAppearance.configureWithOpaqueBackground()
+            navBarAppearance.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : UIColor.mainColor
+            navBarAppearance.titleTextAttributes = attributes
+            nc.navigationBar.standardAppearance = navBarAppearance
+            nc.navigationBar.scrollEdgeAppearance = navBarAppearance
+            let backButton = navigationQLPreviewDocument(title: "Cancel".localized(), style: .plain, target: self, action: #selector(cancelDocumentPreview))
+            vcHandleFile.navigationItem.leftBarButtonItem = backButton
+            let sendButton = navigationQLPreviewDocument(title: "Send".localized(), style: .done, target: self, action: #selector(sendDocument))
+            buttonSpec.setImage(UIImage(named: "pb_ic_attach_spc_off", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal).resize(target: CGSize(width: 30, height: 30)), for: .normal)
+            buttonSpec.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
+            buttonSpec.addTarget(self, action: #selector(showConfigurationFile), for: .touchUpInside)
+            let barButtonItemSpec = UIBarButtonItem(customView: buttonSpec)
+            vcHandleFile.navigationItem.rightBarButtonItems = [sendButton, barButtonItemSpec]
+            backButton.navigation = nc
+            sendButton.navigation = nc
+            if let viewVc = vcHandleFile.view {
+                vcHandleFile.title = self.previewItem?.lastPathComponent
+                vcHandleFile.addChild(previewController)
+                previewController.dataSource = self
+                previewController.view.frame = CGRect(x: 0, y: 0, width: viewVc.bounds.size.width, height: viewVc.bounds.size.height)
+                previewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+                viewVc.addSubview(previewController.view)
+                previewController.didMove(toParent: vcHandleFile)
+                
+                self.present(nc, animated: true)
+            }
+        }
+    }
+    
+    @objc private func showConfigurationFile() {
+        let modalVC = UIViewController()
+        if let viewModal = modalVC.view {
+            viewModal.backgroundColor = .whiteBubbleColor
+            
+            let closeButton = UIButton(type: .close)
+            viewModal.addSubview(closeButton)
+            closeButton.anchor(top: viewModal.topAnchor, right: viewModal.rightAnchor, paddingTop: 15, paddingRight: 15, width: 30, height: 30)
+            closeButton.layer.cornerRadius = 15
+            closeButton.clipsToBounds = true
+            closeButton.backgroundColor = .lightGray.withAlphaComponent(0.1)
+            let config = UIImage.SymbolConfiguration(pointSize: 18, weight: .semibold)
+            closeButton.setImage(UIImage(systemName: "xmark", withConfiguration: config), for: .normal)
+            closeButton.addAction(UIAction { _ in
+                modalVC.dismiss(animated: true)
+            }, for: .touchUpInside)
+            
+            let imageSpec = UIButton(type: .custom)
+            viewModal.addSubview(imageSpec)
+            imageSpec.anchor(top: viewModal.topAnchor, left: viewModal.leftAnchor, paddingTop: 25, paddingLeft: 15, width: 40, height: 40)
+            imageSpec.layer.cornerRadius = 20
+            imageSpec.clipsToBounds = true
+            imageSpec.backgroundColor = .lightGray.withAlphaComponent(0.1)
+            imageSpec.setImage(UIImage(named: "pb_ic_attach_spc", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal).resize(target: CGSize(width: 35, height: 35)), for: .normal)
             
-            self.present(navController, animated: true, completion: nil)
+            let title = UILabel()
+            title.text = "Option for Attachment".localized()
+            viewModal.addSubview(title)
+            title.anchor(top: viewModal.topAnchor, left: imageSpec.rightAnchor, paddingTop: 23, paddingLeft: 10)
+            title.textColor = .label
+            title.font = .boldSystemFont(ofSize: 16)
+            
+            let subtitle = UILabel()
+            subtitle.text = "Select option :".localized()
+            viewModal.addSubview(subtitle)
+            subtitle.anchor(top: title.bottomAnchor, left: imageSpec.rightAnchor, paddingLeft: 10)
+            subtitle.textColor = .gray
+            subtitle.font = .systemFont(ofSize: 14)
+            
+            tableViewConfigFile = UITableView()
+            viewModal.addSubview(tableViewConfigFile)
+            tableViewConfigFile.backgroundColor = .white
+            tableViewConfigFile.layer.cornerRadius = 8.0
+            tableViewConfigFile.clipsToBounds = true
+            tableViewConfigFile.anchor(top: imageSpec.bottomAnchor, left: viewModal.leftAnchor, bottom: viewModal.bottomAnchor, right: viewModal.rightAnchor, paddingTop: 15, paddingLeft: 15, paddingBottom: 80, paddingRight: 15)
+            tableViewConfigFile.register(UITableViewCell.self, forCellReuseIdentifier: "cellConfigFile")
+            tableViewConfigFile.dataSource = self
+            tableViewConfigFile.delegate = self
+            tableViewConfigFile.separatorStyle = .singleLine
+            tableViewConfigFile.tableFooterView = UIView()
+            if #available(iOS 15.0, *) {
+                tableViewConfigFile.sectionHeaderTopPadding = 0
+            }
+            
+            if #available(iOS 15.0, *) {
+                if let sheet = modalVC.sheetPresentationController {
+                    sheet.detents = [.medium()]
+                }
+            } else {
+                // Fallback on earlier versions
+            }
         }
+        UIApplication.shared.visibleViewController?.present(modalVC, animated: true)
     }
     
     @objc private func cancelDocumentPreview(sender: navigationQLPreviewDocument) {
@@ -3450,11 +3547,11 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
                                                                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 ?? "")
+                                                               opposite_pin: dataMessages[indexPath!.row][TypeDataMessage.opposite_pin]  as? String ?? "", specFile: "")
             Nexilis.addQueueMessage(message: message)
         })
         
-        var children: [UIMenuElement] = [star, reply, forward, copy, delete]
+        var children: [UIMenuElement] = [star, reply, copy, delete]
         var isMore = false
 //        let copyOption = self.copyOption(indexPath: indexPath!)
         let idMe = User.getMyPin() as String?
@@ -3466,25 +3563,22 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
         } else if (groupImages[dataMessages[indexPath!.row]["message_id"]  as? String ?? ""] != nil) {
             forward.title = "Forward All".localized()
             delete.title = "Delete All".localized()
-            children = [forward, delete]
-        } 
-        else if dataMessages[indexPath!.row]["f_pin"]  as? String ?? "" == "-999" {
-            children = [star, reply ,delete]
-            if (dataMessages[indexPath!.row]["f_pin"]  as? String ?? "") == idMe {
-                children.insert(info, at: children.count - 1)
+            children = [delete]
+            if Nexilis.checkingAccess(key: "secure_folder_forward") || (dataMessages[indexPath!.row][TypeDataMessage.spec_file] as? String ?? "").contains("forward") {
+                children.insert(forward, at: 0)
             }
-        }
-        else if !(dataMessages[indexPath!.row]["image_id"]  as? String ?? "").isEmpty || !(dataMessages[indexPath!.row]["video_id"]  as? String ?? "").isEmpty || !(dataMessages[indexPath!.row]["file_id"]  as? String ?? "").isEmpty {
-            children = [star, reply, forward ,delete]
-            if (dataMessages[indexPath!.row]["f_pin"]  as? String ?? "") == idMe {
-                children.insert(info, at: children.count - 1)
+        } else {
+            if dataMessages[indexPath!.row]["f_pin"]  as? String ?? "" == "-999" {
+                children = [star, reply ,delete]
             }
-        } else if dataMessages[indexPath!.row]["attachment_flag"]  as? String ?? "" == "11" {
-            children = [reply, delete]
-            if (dataMessages[indexPath!.row]["f_pin"]  as? String ?? "") == idMe {
-                children.insert(info, at: children.count - 1)
+            else if !(dataMessages[indexPath!.row]["image_id"]  as? String ?? "").isEmpty || !(dataMessages[indexPath!.row]["video_id"]  as? String ?? "").isEmpty || !(dataMessages[indexPath!.row]["file_id"]  as? String ?? "").isEmpty {
+                children = [star, reply ,delete]
+            } else if dataMessages[indexPath!.row]["attachment_flag"]  as? String ?? "" == "11" {
+                children = [reply, delete]
+            }
+            if (Nexilis.checkingAccess(key: "secure_folder_forward") && dataMessages[indexPath!.row]["attachment_flag"]  as? String ?? "" != "11") || (!(dataMessages[indexPath!.row][TypeDataMessage.message_text]  as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["image_id"]  as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["video_id"]  as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["file_id"]  as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["audio_id"]  as? String ?? "").isEmpty) || (dataMessages[indexPath!.row][TypeDataMessage.spec_file] as? String ?? "").contains("forward") {
+                children.insert(forward, at: 2)
             }
-        } else {
             if (dataMessages[indexPath!.row]["f_pin"]  as? String ?? "") == idMe {
                 children.insert(info, at: children.count - 1)
             }
@@ -4366,13 +4460,16 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
     }
     
     public func numberOfSections(in tableView: UITableView) -> Int {
-        if tableView == tableMention || tableView == tableMentionEdit {
+        if tableView == tableMention || tableView == tableMentionEdit || tableView == tableViewConfigFile {
             return 1
         }
         return dataDates.count
     }
     
     public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        if tableView == tableViewConfigFile {
+            return 2
+        }
         if tableView == tableMention || tableView == tableMentionEdit {
             return listMentionWithText.count
         }
@@ -4381,6 +4478,9 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
     }
     
     public func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
+        if tableView == tableViewConfigFile {
+            return nil
+        }
         if tableView == tableMention || tableView == tableMentionEdit {
             return .none
         }
@@ -4420,13 +4520,40 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
     }
     
     public func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
-        if tableView == tableMention || tableView == tableMentionEdit {
+        if tableView == tableMention || tableView == tableMentionEdit || tableView == tableViewConfigFile {
             return 0
         }
         return 50
     }
     
     public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        if tableView == tableViewConfigFile {
+            tableView.deselectRow(at: indexPath, animated: true)
+            var type = ""
+            if indexPath.row == 0 {
+                type = "share,download"
+            } else {
+                type = "forward"
+            }
+            if !specFileString.contains(type) {
+                if !specFileString.isEmpty {
+                    specFileString += ","
+                }
+                specFileString += type
+            } else {
+                specFileString = specFileString.replacingOccurrences(of: type, with: "")
+                if specFileString == "," {
+                    specFileString = ""
+                }
+            }
+            if specFileString.isEmpty {
+                buttonSpec.setImage(UIImage(named: "pb_ic_attach_spc_off", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal).resize(target: CGSize(width: 30, height: 30)), for: .normal)
+            } else {
+                buttonSpec.setImage(UIImage(named: "pb_ic_attach_spc", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal).resize(target: CGSize(width: 30, height: 30)), for: .normal)
+            }
+            tableView.reloadData()
+            return
+        }
         if tableView == tableMention || tableView == tableMentionEdit {
             tableView.deselectRow(at: indexPath, animated: true)
             var nowTextField = textFieldSend!
@@ -4487,21 +4614,28 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
             addSubviewMultipleSession()
             return
         }
-        if !(dataMessages[indexPath.row]["image_id"]  as? String ?? "").isEmpty || !(dataMessages[indexPath.row]["video_id"]  as? String ?? "").isEmpty || !(dataMessages[indexPath.row]["file_id"]  as? String ?? "").isEmpty {
-            var file = dataMessages[indexPath.row]["image_id"]  as? String ?? ""
-            if file.isEmpty {
-                file = dataMessages[indexPath.row]["video_id"]  as? String ?? ""
+        if !(dataMessages[indexPath.row]["image_id"]  as? String ?? "").isEmpty || !(dataMessages[indexPath.row]["video_id"]  as? String ?? "").isEmpty || !(dataMessages[indexPath.row]["file_id"]  as? String ?? "").isEmpty || !(dataMessages[indexPath.row]["audio_id"]  as? String ?? "").isEmpty {
+            if !Nexilis.checkingAccess(key: "secure_folder_forward") && !(dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String ?? "").contains("forward") {
+                return
+            } else {
+                var file = dataMessages[indexPath.row]["image_id"]  as? String ?? ""
                 if file.isEmpty {
-                    file = dataMessages[indexPath.row]["file_id"]  as? String ?? ""
+                    file = dataMessages[indexPath.row]["video_id"]  as? String ?? ""
+                    if file.isEmpty {
+                        file = dataMessages[indexPath.row]["file_id"]  as? String ?? ""
+                        if file.isEmpty {
+                            file = dataMessages[indexPath.row]["audio_id"]  as? String ?? ""
+                        }
+                    }
                 }
-            }
-            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(file)
-                if !FileManager.default.fileExists(atPath: fileURL.path) && !FileEncryption.shared.isSecureExists(filename: fileURL.lastPathComponent) {
-                    return
+                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(file)
+                    if !FileManager.default.fileExists(atPath: fileURL.path) && !FileEncryption.shared.isSecureExists(filename: fileURL.lastPathComponent) {
+                        return
+                    }
                 }
             }
         }
@@ -4549,6 +4683,26 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
     
     
     public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        if tableView == tableViewConfigFile {
+            let cell = tableView.dequeueReusableCell(withIdentifier: "cellConfigFile", for: indexPath as IndexPath)
+            var content = cell.defaultContentConfiguration()
+            content.textProperties.font = .systemFont(ofSize: 16, weight: .medium)
+            content.textProperties.color = .label
+            content.secondaryTextProperties.font = .systemFont(ofSize: 14)
+            content.secondaryTextProperties.color = .gray
+            if indexPath.row == 0 {
+                content.text = "Can Share and Download".localized()
+                content.secondaryText = "The user, as the receiver, can share and download the attachment.".localized()
+                cell.accessoryType = specFileString.contains("share,download") ? .checkmark : .none
+            } else {
+                content.text = "Can Forward".localized()
+                content.secondaryText = "The user, as the receiver, can forward the attachment.".localized()
+                cell.accessoryType = specFileString.contains("forward") ? .checkmark : .none
+            }
+            cell.contentConfiguration = content
+            cell.tintColor = .black
+            return cell
+        }
         if tableView == tableMention || tableView == tableMentionEdit {
             let cellMention = tableView.dequeueReusableCell(withIdentifier: tableView == tableMention ? "cellMention" : "cellEditMention", for: indexPath as IndexPath)
             var content = cellMention.defaultContentConfiguration()
@@ -4627,20 +4781,27 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
         if (dataMessages[indexPath.row]["attachment_flag"] as? String == "0" && dataMessages[indexPath.row]["lock"] as? String != "1") || forwardSession || deleteSession {
             var showSelectedImage = true
             if (!imageChat.isEmpty || !videoChat.isEmpty || !fileChat.isEmpty) && forwardSession {
-                var file = dataMessages[indexPath.row]["image_id"]  as? String ?? ""
-                if file.isEmpty {
-                    file = dataMessages[indexPath.row]["video_id"]  as? String ?? ""
+                if !Nexilis.checkingAccess(key: "secure_folder_forward") && !(dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String ?? "").contains("forward") {
+                    showSelectedImage = false
+                } else {
+                    var file = dataMessages[indexPath.row]["image_id"]  as? String ?? ""
                     if file.isEmpty {
-                        file = dataMessages[indexPath.row]["file_id"]  as? String ?? ""
+                        file = dataMessages[indexPath.row]["video_id"]  as? String ?? ""
+                        if file.isEmpty {
+                            file = dataMessages[indexPath.row]["file_id"]  as? String ?? ""
+                            if file.isEmpty {
+                                file = dataMessages[indexPath.row]["audio_id"]  as? String ?? ""
+                            }
+                        }
                     }
-                }
-                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(file)
-                    if !FileManager.default.fileExists(atPath: fileURL.path) && !FileEncryption.shared.isSecureExists(filename: fileURL.lastPathComponent) {
-                        showSelectedImage = false
+                    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(file)
+                        if !FileManager.default.fileExists(atPath: fileURL.path) && !FileEncryption.shared.isSecureExists(filename: fileURL.lastPathComponent) {
+                            showSelectedImage = false
+                        }
                     }
                 }
             }
@@ -4853,6 +5014,8 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
         }
         
         let imageStared = UIImageView()
+        let imageAckView = UIImageView()
+        let imageCredentialView = UIImageView()
         if dataMessages[indexPath.row]["is_stared"] as? String == "1" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"]  as? String ?? "" == "0") {
             cellMessage.contentView.addSubview(imageStared)
             imageStared.translatesAutoresizingMaskIntoConstraints = false
@@ -4871,7 +5034,6 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
         }
         
         if dataMessages[indexPath.row]["read_receipts"] as? String == "8" {
-            let imageAckView = UIImageView()
             var imageAck = UIImage(named: "ack_icon_gray", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
             cellMessage.contentView.addSubview(imageAckView)
             imageAckView.translatesAutoresizingMaskIntoConstraints = false
@@ -4900,7 +5062,6 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
         }
         
         if (dataMessages[indexPath.row]["credential"] as? String) == "1" && (dataMessages[indexPath.row]["lock"] as? String) != "2" && (dataMessages[indexPath.row]["lock"] as? String) != "1" {
-            let imageCredentialView = UIImageView()
             let imageCredential = UIImage(named: "confidential_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
             imageCredentialView.image = imageCredential
             cellMessage.contentView.addSubview(imageCredentialView)
@@ -4916,20 +5077,34 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
             }
         }
         
-//        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 + offset(), 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
-//        }
+        if !(dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String ?? "").isEmpty && (dataMessages[indexPath.row]["lock"] as? String) != "2" && (dataMessages[indexPath.row]["lock"] as? String) != "1" {
+            let imageSpecFileView = UIImageView()
+            let imageSpecFile = UIImage(named: "pb_ic_attach_spc", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
+            imageSpecFileView.image = imageSpecFile
+            cellMessage.contentView.addSubview(imageSpecFileView)
+            imageSpecFileView.translatesAutoresizingMaskIntoConstraints = false
+            imageSpecFileView.widthAnchor.constraint(equalToConstant: 30).isActive = true
+            imageSpecFileView.heightAnchor.constraint(equalToConstant: 30).isActive = true
+            imageSpecFileView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
+            if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
+                if imageAckView.isDescendant(of: cellMessage.contentView) {
+                    imageSpecFileView.leadingAnchor.constraint(equalTo: imageAckView.trailingAnchor, constant: 5).isActive = true
+                } else if imageCredentialView.isDescendant(of: cellMessage.contentView) {
+                    imageSpecFileView.leadingAnchor.constraint(equalTo: imageCredentialView.trailingAnchor, constant: 5).isActive = true
+                } else {
+                    imageSpecFileView.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 30).isActive = true
+                }
+            } else {
+                if imageAckView.isDescendant(of: cellMessage.contentView) {
+                    imageSpecFileView.trailingAnchor.constraint(equalTo: imageAckView.leadingAnchor, constant: -5).isActive = true
+                } else if imageCredentialView.isDescendant(of: cellMessage.contentView) {
+                    imageSpecFileView.trailingAnchor.constraint(equalTo: imageCredentialView.leadingAnchor, constant: -5).isActive = true
+                } else {
+                    imageSpecFileView.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -30).isActive = true
+                }
+            }
+        }
+        
         if dataMessages[indexPath.row]["attachment_flag"]  as? String ?? "" == "27" || dataMessages[indexPath.row]["attachment_flag"]  as? String ?? "" == "26" {
             messageText.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 85).isActive = true
             let imageLS = UIImageView()
@@ -5542,11 +5717,13 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                 
                 if !copySession && !forwardSession && !deleteSession {
                     let objectTap = ObjectGesture(target: self, action: #selector(contentMessageTapped(_:)))
+                    let sfs = (dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String) ?? ""
                     imageThumb.isUserInteractionEnabled = true
                     imageThumb.addGestureRecognizer(objectTap)
                     objectTap.image_id = imageChat
                     objectTap.video_id = videoChat
                     objectTap.gif_id = gifChat
+                    objectTap.specFile = sfs
                     objectTap.imageView = imageThumb
                     objectTap.indexPath = indexPath
                 }
@@ -5684,10 +5861,12 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
             
             if !copySession && !forwardSession && !deleteSession {
                 let objectTap = ObjectGesture(target: self, action: #selector(contentMessageTapped(_:)))
+                let sfs = (dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String) ?? ""
                 containerViewFile.addGestureRecognizer(objectTap)
                 objectTap.containerFile = containerViewFile
                 objectTap.labelFile = nameFile
                 objectTap.file_id = fileChat
+                objectTap.specFile = sfs
                 objectTap.indexPath = indexPath
             }
         }
@@ -6296,17 +6475,62 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
         let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
         let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
         let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
+        func showMedia(data: Data? = nil, url: URL? = nil, type: Int = 0) {
+            let image = UIImage(data: data ?? Data())
+            let imageViewer = MediaViewerViewController()
+            if type == 0 {
+                imageViewer.media = .image(image ?? UIImage())
+            } else if type == 1 {
+                imageViewer.media = .video(url ?? URL(string: "")!)
+            } else if type == 2 {
+                imageViewer.media = .gif(UIImage.gifImageWithData(data ?? Data()) ?? UIImage())
+            }
+            
+            let navigationController = UINavigationController(rootViewController: imageViewer)
+            navigationController.defaultStyle()
+            navigationController.view.backgroundColor = .clear
+            navigationController.modalPresentationCapturesStatusBarAppearance = true
+            navigationController.modalPresentationStyle = .overFullScreen
+            
+            let backAction = UIAction { _ in
+                navigationController.dismiss(animated: true)
+            }
+            let backButton = UIBarButtonItem(title: nil, image: UIImage(systemName: "chevron.backward"), primaryAction: backAction, menu: nil)
+            imageViewer.navigationItem.leftBarButtonItem = backButton
+            if Nexilis.checkingAccess(key: "secure_folder_share") || sender.specFile.contains("download") || sender.specFile.contains("share") {
+                let shareAction = UIAction { _ in
+                    var activityViewController = UIActivityViewController(activityItems: [image ?? UIImage()], applicationActivities: nil)
+                    if type == 1 {
+                        activityViewController = UIActivityViewController(activityItems: [url ?? URL(string: "")!], applicationActivities: nil)
+                    }
+                    activityViewController.popoverPresentationController?.sourceView = imageViewer.view
+                    imageViewer.present(activityViewController, animated: true, completion: nil)
+                }
+                let shareButton = UIBarButtonItem(title: nil, image: UIImage(systemName: "square.and.arrow.up"), primaryAction: shareAction, menu: nil)
+                imageViewer.navigationItem.rightBarButtonItem = shareButton
+            }
+            
+            var name = (dataGroup["f_name"] as? String ?? "") + " (\(dataTopic["title"] as? String ?? ""))"
+            imageViewer.title = name
+            
+            let transitionDelegate = ZoomTransitioningDelegate()
+            transitionDelegate.originImageView = sender.imageView
+            navigationController.transitioningDelegate = transitionDelegate
+            self.transitioningDelegateRef = transitionDelegate
+            
+            present(navigationController, animated: true) {
+                imageViewer.animateBackgroundIn()
+            }
+        }
         if (sender.image_id != "") {
             if let dirPath = paths.first {
                 let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.image_id)
                 if FileManager.default.fileExists(atPath: imageURL.path) {
-                    let image    = UIImage(contentsOfFile: imageURL.path)
-                    let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
-                    previewImageVC.image = image
-                    previewImageVC.isHiddenTextField = true
-                    previewImageVC.modalPresentationStyle = .custom
-                    previewImageVC.modalTransitionStyle  = .crossDissolve
-                    self.present(previewImageVC, animated: true, completion: nil)
+                    do {
+                        showMedia(data: try Data(contentsOf: imageURL))
+                    } catch {
+                        
+                    }
                 } else if FileEncryption.shared.isSecureExists(filename: sender.image_id) {
                     do {
                         if var data = try FileEncryption.shared.readSecure(filename: sender.image_id) {
@@ -6314,13 +6538,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                             if dataDecrypt != nil {
                                 data = dataDecrypt!
                             }
-                            let image = UIImage(data: data)
-                            let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
-                            previewImageVC.image = image
-                            previewImageVC.isHiddenTextField = true
-                            previewImageVC.modalPresentationStyle = .custom
-                            previewImageVC.modalTransitionStyle  = .crossDissolve
-                            self.present(previewImageVC, animated: true, completion: nil)
+                            showMedia(data: data)
                         }
                     }
                     catch {
@@ -6342,36 +6560,6 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                     Download().startHTTP(forKey: sender.image_id) { (name, progress) in
                         guard progress == 100 else {
                             return
-                        }
-                        do {
-                            let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
-                            let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
-                            let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
-                            if let dirPath = paths.first {
-                                let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.image_id)
-                                if FileManager.default.fileExists(atPath: imageURL.path) {
-                                    let image    = UIImage(contentsOfFile: imageURL.path)
-                                    let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
-                                    if save {
-                                        UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
-                                    }
-                                }
-                                else if FileEncryption.shared.isSecureExists(filename: sender.image_id) {
-                                    if var secureData = try FileEncryption.shared.readSecure(filename: sender.image_id) {
-                                        let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: secureData)
-                                        if dataDecrypt != nil {
-                                            secureData = dataDecrypt!
-                                        }
-                                        let image = UIImage(data: secureData)
-                                        let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
-                                        if save {
-                                            UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
-                                        }
-                                    }
-                                }
-                            }
-                        } catch {
-                            
                         }
                         DispatchQueue.main.async {
                             activityIndicator.stopAnimating()
@@ -6386,7 +6574,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                 if FileManager.default.fileExists(atPath: gifURL.path) {
                     do {
                         let data = try Data(contentsOf: gifURL)
-                        APIS.openImageNexilis(image: UIImage(), data: data, isGIF: true)
+                        showMedia(data: data, type: 2)
                     } catch {
                         
                     }
@@ -6397,7 +6585,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                             if dataDecrypt != nil {
                                 secureData = dataDecrypt!
                             }
-                            APIS.openImageNexilis(image: UIImage(), data: secureData, isGIF: true)
+                            showMedia(data: secureData, type: 2)
                         }
                     } catch {
                         
@@ -6408,11 +6596,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
             if let dirPath = paths.first {
                 let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.video_id)
                 if FileManager.default.fileExists(atPath: videoURL.path) {
-                    let player = AVPlayer(url: videoURL as URL)
-                    let playerVC = AVPlayerViewController()
-                    playerVC.modalPresentationStyle = .custom
-                    playerVC.player = player
-                    self.present(playerVC, animated: true, completion: nil)
+                    showMedia(url: videoURL, type: 1)
                 } else if FileEncryption.shared.isSecureExists(filename: sender.video_id) {
                     do {
                         if var secureData = try FileEncryption.shared.readSecure(filename: sender.video_id) {
@@ -6423,11 +6607,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                             let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
                             let tempPath = cachesDirectory.appendingPathComponent(sender.video_id)
                             try secureData.write(to: tempPath)
-                            let player = AVPlayer(url: tempPath as URL)
-                            let playerVC = AVPlayerViewController()
-                            playerVC.modalPresentationStyle = .custom
-                            playerVC.player = player
-                            self.present(playerVC, animated: true, completion: nil)
+                            showMedia(url: tempPath, type: 1)
                         }
                     } catch {
                         
@@ -6478,61 +6658,67 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                                 shapeLoading.strokeEnd = CGFloat(progress / 100)
                                 return
                             }
-                            do {
-                                if FileManager.default.fileExists(atPath: videoURL.path) {
-                                    let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
-                                    if save {
-                                        let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.video_id)
-                                        PHPhotoLibrary.shared().performChanges({
-                                            PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoURL)
-                                        }) { saved, error in
-                                            
-                                        }
-                                    }
-                                }
-                                else if FileEncryption.shared.isSecureExists(filename: videoURL.lastPathComponent) {
-                                    if var secureData = try FileEncryption.shared.readSecure(filename: videoURL.lastPathComponent) {
-                                        let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: secureData)
-                                        if dataDecrypt != nil {
-                                            secureData = dataDecrypt!
-                                        }
-                                        let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
-                                        let tempPath = cachesDirectory.appendingPathComponent(name)
-                                        try secureData.write(to: tempPath)
-                                        let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
-                                        if save {
-                                            PHPhotoLibrary.shared().performChanges({
-                                                PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: tempPath)
-                                            }) { saved, error in
-                                                
-                                            }
-                                        }
-                                    }
-                                }
-                                let idx = self.dataMessages.firstIndex(where: { $0["video_id"]  as? String ?? "" == sender.video_id})
-                                if idx != nil {
-                                    self.dataMessages[idx!]["progress"] = progress
-                                    self.tableChatView.reloadRows(at: [sender.indexPath], with: .none)
-                                }
-                            }
-                            catch {
-                                
+                            let idx = self.dataMessages.firstIndex(where: { $0["video_id"]  as? String ?? "" == sender.video_id})
+                            if idx != nil {
+                                self.dataMessages[idx!]["progress"] = progress
+                                self.tableChatView.reloadRows(at: [sender.indexPath], with: .none)
                             }
                         }
                     }
                 }
             }
         } else if (sender.file_id != "") {
+            func showFile(urlFile: URL) {
+                let previewController = QLPreviewController()
+                previewController.dataSource = self
+                let vcHandleFile = UIViewController()
+                let nc = UINavigationController(rootViewController: vcHandleFile)
+                let attributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
+                let navBarAppearance = UINavigationBarAppearance()
+                nc.defaultStyle()
+                navBarAppearance.configureWithOpaqueBackground()
+                navBarAppearance.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : UIColor.mainColor
+                navBarAppearance.titleTextAttributes = attributes
+                nc.navigationBar.standardAppearance = navBarAppearance
+                nc.navigationBar.scrollEdgeAppearance = navBarAppearance
+                let backAction = UIAction { _ in
+                    nc.dismiss(animated: true)
+                }
+                let backButton = UIBarButtonItem(title: nil, image: UIImage(systemName: "chevron.backward"), primaryAction: backAction, menu: nil)
+                vcHandleFile.navigationItem.leftBarButtonItem = backButton
+                if Nexilis.checkingAccess(key: "secure_folder_share") || sender.specFile.contains("download") || sender.specFile.contains("share") {
+                    let shareAction = UIAction { _ in
+                        let fileManager = FileManager.default
+                        let tempURL = fileManager.temporaryDirectory.appendingPathComponent(sender.labelFile.text ?? "")
+                        do {
+                            try fileManager.copyItem(at: urlFile, to: tempURL)
+                            let activityViewController = UIActivityViewController(activityItems: [tempURL], applicationActivities: nil)
+                            activityViewController.popoverPresentationController?.sourceView = vcHandleFile.view
+                            vcHandleFile.present(activityViewController, animated: true, completion: nil)
+                        } catch {
+                            
+                        }
+                    }
+                    let shareButton = UIBarButtonItem(title: nil, image: UIImage(systemName: "square.and.arrow.up"), primaryAction: shareAction, menu: nil)
+                    vcHandleFile.navigationItem.rightBarButtonItem = shareButton
+                }
+                if let viewVc = vcHandleFile.view {
+                    vcHandleFile.title = sender.labelFile.text
+                    vcHandleFile.addChild(previewController)
+                    previewController.dataSource = self
+                    previewController.view.frame = CGRect(x: 0, y: 0, width: viewVc.bounds.size.width, height: viewVc.bounds.size.height)
+                    previewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+                    viewVc.addSubview(previewController.view)
+                    previewController.didMove(toParent: vcHandleFile)
+                    
+                    self.present(nc, animated: 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 = CustomQLPreviewController()
-//                            let rightBarButton = UIBarButtonItem()
-//                            previewController.navigationItem.rightBarButtonItem = rightBarButton
-                    previewController.dataSource = self
-//                            previewController.modalPresentationStyle = .custom
-                    self.present(previewController,animated: true)
+                    showFile(urlFile: fileURL)
                 } else if FileEncryption.shared.isSecureExists(filename: sender.file_id) {
                     do {
                         if var docData = try FileEncryption.shared.readSecure(filename: sender.file_id) {
@@ -6544,12 +6730,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                             let tempPath = cachesDirectory.appendingPathComponent(sender.file_id)
                             try docData.write(to: tempPath)
                             self.previewItem = tempPath as NSURL
-                            let previewController = CustomQLPreviewController()
-//                            let rightBarButton = UIBarButtonItem()
-//                            previewController.navigationItem.rightBarButtonItem = rightBarButton
-                            previewController.dataSource = self
-//                            previewController.modalPresentationStyle = .custom
-                            self.present(previewController,animated: true)
+                            showFile(urlFile: tempPath)
                         }
                     }
                     catch {

+ 404 - 186
NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift

@@ -134,6 +134,11 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
     
     var downloadList: [String: IndexPath] = [:]
     
+    var transitioningDelegateRef: ZoomTransitioningDelegate?
+    var buttonSpec = UIButton(type: .custom)
+    var tableViewConfigFile: UITableView!
+    var specFileString = ""
+    
     func offset() -> CGFloat{
         guard let fontSize = Int(SecureUserDefaults.shared.value(forKey: "font_size") ?? "0") else { return 0 }
         return CGFloat(fontSize)
@@ -950,7 +955,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
     private func addDataMessage() {
         multipleOffsetUp += 1
         let queryCount = "SELECT COUNT(*) FROM MESSAGE where (f_pin='\(dataPerson["f_pin"]!!)' or l_pin='\(dataPerson["f_pin"]!!)') AND (message_scope_id = '\(MessageScope.WHISPER)' OR message_scope_id = '\(MessageScope.FORM)' OR message_scope_id = '\(MessageScope.CALL)' OR message_scope_id = '\(MessageScope.MISSED_CALL)') AND is_call_center = 0"
-        let 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, last_edited, gif_id, is_forwarded_message FROM MESSAGE where (f_pin='\(dataPerson["f_pin"]!!)' or l_pin='\(dataPerson["f_pin"]!!)') AND (message_scope_id = '\(MessageScope.WHISPER)' OR message_scope_id = '\(MessageScope.FORM)' OR message_scope_id = '\(MessageScope.CALL)' OR message_scope_id = '\(MessageScope.MISSED_CALL)') AND is_call_center = 0 order by server_date asc LIMIT CASE WHEN (\(queryCount))-\(dataMessages.count)>=20 THEN 20*\(multipleOffsetUp-1) ELSE (\(queryCount))-\(dataMessages.count) END OFFSET CASE WHEN (\(queryCount))>=\(20*multipleOffsetUp) THEN (\(queryCount))-\(20*multipleOffsetUp) ELSE 0 END"
+        let 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, last_edited, gif_id, is_forwarded_message, attachment_speciality FROM MESSAGE where (f_pin='\(dataPerson["f_pin"]!!)' or l_pin='\(dataPerson["f_pin"]!!)') AND (message_scope_id = '\(MessageScope.WHISPER)' OR message_scope_id = '\(MessageScope.FORM)' OR message_scope_id = '\(MessageScope.CALL)' OR message_scope_id = '\(MessageScope.MISSED_CALL)') AND is_call_center = 0 order by server_date asc LIMIT CASE WHEN (\(queryCount))-\(dataMessages.count)>=20 THEN 20*\(multipleOffsetUp-1) ELSE (\(queryCount))-\(dataMessages.count) END OFFSET CASE WHEN (\(queryCount))>=\(20*multipleOffsetUp) THEN (\(queryCount))-\(20*multipleOffsetUp) ELSE 0 END"
         Database.shared.database?.inTransaction({ (fmdb, rollback) in
             do {
                 if let cursorData = Database.shared.getRecords(fmdb: fmdb, query: query) {
@@ -983,6 +988,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                         row[TypeDataMessage.last_edit] = cursorData.longLongInt(forColumnIndex: 23)
                         row[TypeDataMessage.gif_id] = cursorData.string(forColumnIndex: 24)
                         row[TypeDataMessage.is_forwarded] = Int(cursorData.int(forColumnIndex: 25))
+                        row[TypeDataMessage.spec_file] = cursorData.string(forColumnIndex: 26)
                         if let cursorStatus = Database.shared.getRecords(fmdb: fmdb, query: "SELECT status FROM MESSAGE_STATUS WHERE message_id='\(row["message_id"]  as? String ?? "")'") {
                             while cursorStatus.next() {
                                 row["status"] = cursorStatus.string(forColumnIndex: 0)
@@ -1094,7 +1100,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
     private func getData() {
 //        let queryCount = "SELECT COUNT(*) FROM MESSAGE where (f_pin='\(dataPerson["f_pin"]!!)' or l_pin='\(dataPerson["f_pin"]!!)') AND (message_scope_id = '3' OR message_scope_id = '18') AND is_call_center = 0"
 //        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 FROM MESSAGE where (f_pin='\(dataPerson["f_pin"]!!)' or l_pin='\(dataPerson["f_pin"]!!)') AND (message_scope_id = '3' OR message_scope_id = '18') AND is_call_center = 0 order by server_date asc LIMIT CASE WHEN (\(queryCount))-\(dataMessages.count)>=20 THEN 20 ELSE (\(queryCount))-\(dataMessages.count) END OFFSET CASE WHEN (\(queryCount))>=\(20*multipleOffsetUp) THEN (\(queryCount))-\(20*multipleOffsetUp) ELSE 0 END"
-        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, last_edited, gif_id, is_forwarded_message FROM MESSAGE where (f_pin='\(dataPerson["f_pin"]!!)' or l_pin='\(dataPerson["f_pin"]!!)') AND (message_scope_id = '\(MessageScope.WHISPER)' OR message_scope_id = '\(MessageScope.FORM)' OR message_scope_id = '\(MessageScope.CALL)' OR message_scope_id = '\(MessageScope.MISSED_CALL)') AND is_call_center = 0 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, is_call_center, call_center_id, opposite_pin, last_edited, gif_id, is_forwarded_message, attachment_speciality FROM MESSAGE where (f_pin='\(dataPerson["f_pin"]!!)' or l_pin='\(dataPerson["f_pin"]!!)') AND (message_scope_id = '\(MessageScope.WHISPER)' OR message_scope_id = '\(MessageScope.FORM)' OR message_scope_id = '\(MessageScope.CALL)' OR message_scope_id = '\(MessageScope.MISSED_CALL)') AND is_call_center = 0 order by server_date asc"
         if isContactCenter {
             if complaintId.isEmpty {
                 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 (f_pin='\(dataPerson["f_pin"]!!)' or l_pin='\(dataPerson["f_pin"]!!)') AND message_scope_id = '\(MessageScope.CHATROOM)' AND broadcast_flag = 0 AND is_call_center = 1 order by server_date asc"
@@ -1161,6 +1167,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                         row[TypeDataMessage.last_edit] = cursorData.longLongInt(forColumnIndex: 23)
                         row[TypeDataMessage.gif_id] = cursorData.string(forColumnIndex: 24)
                         row[TypeDataMessage.is_forwarded] = Int(cursorData.int(forColumnIndex: 25))
+                        row[TypeDataMessage.spec_file] = cursorData.string(forColumnIndex: 26)
                         if let cursorStatus = Database.shared.getRecords(fmdb: fmdb, query: "SELECT status FROM MESSAGE_STATUS WHERE message_id='\(row["message_id"] as? String ?? "")'") {
                             while cursorStatus.next() {
                                 row["status"] = cursorStatus.string(forColumnIndex: 0)
@@ -1677,6 +1684,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                     row["reff_id"] = chatData[CoreMessage_TMessageKey.REF_ID] ?? ""
                     row["lock"] = ""
                     row["is_stared"] = "0"
+                    row[TypeDataMessage.spec_file] = chatData[CoreMessage_TMessageKey.ATTACHMENT_SPECIALITY]
                     row[TypeDataMessage.is_forwarded] = Int(chatData[CoreMessage_TMessageKey.IS_FORWARDED_MESSAGE] ?? "0")
                     row["isSelected"] = false
                     if !self.dataDates.contains("Today".localized()) {
@@ -1932,7 +1940,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
     
     private func appendNewMessage(messageId: String) {
         Database.shared.database?.inTransaction({ (fmdb, rollback) in
-            if let cursorData = Database.shared.getRecords(fmdb: fmdb, 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, last_edited, gif_id, is_forwarded_message from MESSAGE where message_id = '\(messageId)'"), cursorData.next() {
+            if let cursorData = Database.shared.getRecords(fmdb: fmdb, 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, last_edited, gif_id, is_forwarded_message, attachment_speciality from MESSAGE where message_id = '\(messageId)'"), cursorData.next() {
                 var row: [String: Any?] = [:]
                 row["message_id"] = cursorData.string(forColumnIndex: 0)
                 row["f_pin"] = cursorData.string(forColumnIndex: 1)
@@ -1960,6 +1968,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                 row[TypeDataMessage.last_edit] = cursorData.longLongInt(forColumnIndex: 23)
                 row[TypeDataMessage.gif_id] = cursorData.string(forColumnIndex: 24)
                 row[TypeDataMessage.is_forwarded] = Int(cursorData.int(forColumnIndex: 25))
+                row[TypeDataMessage.spec_file] = cursorData.string(forColumnIndex: 26)
                 row["progress"] = 0.0
                 row["isSelected"] = false
                 if !self.dataDates.contains("Today".localized()) {
@@ -2204,7 +2213,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
             case "image":
                 var config = PHPickerConfiguration()
                 config.filter = .images
-                config.preferredAssetRepresentationMode = .current
+                config.preferredAssetRepresentationMode = .automatic
                 let picker = PHPickerViewController(configuration: config)
                 picker.delegate = self
                 if UIBarButtonItem.appearance().titleTextAttributes(for: .normal) != nil {
@@ -2218,7 +2227,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
             case "video":
                 var config = PHPickerConfiguration()
                 config.filter = .videos
-                config.preferredAssetRepresentationMode = .current
+                config.preferredAssetRepresentationMode = .automatic
                 let picker = PHPickerViewController(configuration: config)
                 picker.delegate = self
                 if UIBarButtonItem.appearance().titleTextAttributes(for: .normal) != nil {
@@ -2728,7 +2737,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
             is_secret = 1
         }
         sendTyping(l_pin: l_pin, isTyping: true)
-        let message = CoreMessage_TMessageBank.sendMessage(l_pin: l_pin, message_scope_id: message_scope_id, status: status, message_text: message_text, credential: credential, attachment_flag: attachment_flag, ex_blog_id: ex_blog_id, message_large_text: message_large_text, ex_format: ex_format, image_id: image_id, audio_id: audio_id, video_id: video_id, file_id: file_id, thumb_id: thumb_id, reff_id: reff_id, read_receipts: read_receipts, chat_id: chat_id, is_call_center: is_call_center, call_center_id: call_center_id, opposite_pin: opposite_pin, gif_id: gif_id, isForwarded: "\(is_forwarded)", isSecret: "\(is_secret)")
+        let message = CoreMessage_TMessageBank.sendMessage(l_pin: l_pin, message_scope_id: message_scope_id, status: status, message_text: message_text, credential: credential, attachment_flag: attachment_flag, ex_blog_id: ex_blog_id, message_large_text: message_large_text, ex_format: ex_format, image_id: image_id, audio_id: audio_id, video_id: video_id, file_id: file_id, thumb_id: thumb_id, reff_id: reff_id, read_receipts: read_receipts, chat_id: chat_id, is_call_center: is_call_center, call_center_id: call_center_id, opposite_pin: opposite_pin, gif_id: gif_id, isForwarded: "\(is_forwarded)", isSecret: "\(is_secret)", specFile: specFileString)
         Nexilis.addQueueMessage(message: message)
         let messageId = String(message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID]!)
         if credential == "1" {
@@ -2762,6 +2771,8 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
         row[TypeDataMessage.is_call_center] = is_call_center
         row[TypeDataMessage.call_center_id] = call_center_id
         row[TypeDataMessage.opposite_pin] = opposite_pin
+        row[TypeDataMessage.spec_file] = specFileString
+        specFileString = ""
         if !dataDates.contains("Today".localized()) {
             dataDates.append("Today".localized())
             tableChatView.insertSections(IndexSet(integer: dataDates.count - 1), with: .none)
@@ -3690,7 +3701,7 @@ extension EditorPersonal: PreviewAttachmentImageVideoDelegate, PHPickerViewContr
         }
         if result.itemProvider.hasItemConformingToTypeIdentifier("com.compuserve.gif") {
             picker.dismiss(animated: true, completion: {
-                Nexilis.showLoader()
+                Nexilis.showLoader(text: "Preparing...".localized())
                 result.itemProvider.loadDataRepresentation(forTypeIdentifier: "com.compuserve.gif") { data, error in
                     if let error = error {
                         print("Error loading GIF: \(error.localizedDescription)")
@@ -3719,11 +3730,10 @@ extension EditorPersonal: PreviewAttachmentImageVideoDelegate, PHPickerViewContr
             })
         } else if result.itemProvider.hasItemConformingToTypeIdentifier("public.image") {
             picker.dismiss(animated: true, completion: {
-                Nexilis.showLoader()
-                result.itemProvider.loadFileRepresentation(forTypeIdentifier: "public.image") { url, error in
-                    if let url = url {
+                Nexilis.showLoader(text: "Preparing...".localized())
+                result.itemProvider.loadDataRepresentation(forTypeIdentifier: "public.image") { data, error in
+                    if let data = data {
                         do {
-                            let data = try Data(contentsOf: url)
                             DispatchQueue.main.async {
                                 Nexilis.hideLoader {
                                     let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
@@ -3749,17 +3759,21 @@ extension EditorPersonal: PreviewAttachmentImageVideoDelegate, PHPickerViewContr
             })
         } else if result.itemProvider.hasItemConformingToTypeIdentifier("public.movie") {
             picker.dismiss(animated: true, completion: {
-                Nexilis.showLoader()
-                result.itemProvider.loadFileRepresentation(forTypeIdentifier: "public.movie") { tempURL, error in
-                    if let tempURL = tempURL {
+                Nexilis.showLoader(text: "Preparing...".localized())
+                result.itemProvider.loadFileRepresentation(forTypeIdentifier: "public.movie") { url, error in
+                    if let url = url {
                         let fileManager = FileManager.default
                         let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
-                        let destinationURL = documentsDirectory.appendingPathComponent(tempURL.lastPathComponent)
+                        var nameFile = url.lastPathComponent
+                        if nameFile.contains("&uuid"){
+                            nameFile = UUID().uuidString + ".mov"
+                        }
+                        let destinationURL = documentsDirectory.appendingPathComponent(nameFile)
                         do {
                             if fileManager.fileExists(atPath: destinationURL.path) {
                                 try fileManager.removeItem(at: destinationURL)
                             }
-                            try fileManager.copyItem(at: tempURL, to: destinationURL)
+                            try fileManager.copyItem(at: url, to: destinationURL)
                             DispatchQueue.main.async {
                                 Nexilis.hideLoader {
                                     let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
@@ -3784,7 +3798,8 @@ extension EditorPersonal: PreviewAttachmentImageVideoDelegate, PHPickerViewContr
         }
     }
     
-    func sendChatFromPreviewImage(message_text: String, attachment_flag: String, image_id: String, video_id: String, thumb_id: String, gif_id: String, viewController: UIViewController) {
+    func sendChatFromPreviewImage(message_text: String, attachment_flag: String, image_id: String, video_id: String, thumb_id: String, gif_id: String, viewController: UIViewController, specFile: String) {
+        specFileString = specFile
         sendChat(message_text: message_text, attachment_flag: attachment_flag, image_id: image_id, video_id: video_id, thumb_id: thumb_id, viewController: viewController, gif_id : gif_id)
     }
 }
@@ -3794,22 +3809,107 @@ extension EditorPersonal: UIDocumentPickerDelegate, DocumentPickerDelegate, QLPr
     public func didSelectDocument(document: Any?) {
         if (document != nil) {
             self.previewItem = (document as! [URL])[0] as NSURL
+            specFileString = ""
             let previewController = QLPreviewController()
-            let navController = CustomNavigationController(rootViewController: previewController)
-            navController.navigationBar.tintColor = .black
-            let cancelButtonAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: UIColor.black, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
-            UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes, for: .normal)
-            let leftBarButton = navigationQLPreviewDocument(title: "Cancel".localized(), style: .plain, target: self, action: #selector(cancelDocumentPreview))
-            let rightBarButton = navigationQLPreviewDocument(title: "Send".localized(), style: .done, target: self, action: #selector(sendDocument))
-            leftBarButton.navigation = navController
-            rightBarButton.navigation = navController
-            previewController.navigationItem.leftBarButtonItem = leftBarButton
-            previewController.navigationItem.rightBarButtonItem = rightBarButton
             previewController.dataSource = self
-            previewController.modalPresentationStyle = .pageSheet
+            let vcHandleFile = UIViewController()
+            let nc = UINavigationController(rootViewController: vcHandleFile)
+            let attributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
+            let navBarAppearance = UINavigationBarAppearance()
+            nc.defaultStyle()
+            nc.modalPresentationStyle = .pageSheet
+            navBarAppearance.configureWithOpaqueBackground()
+            navBarAppearance.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : UIColor.mainColor
+            navBarAppearance.titleTextAttributes = attributes
+            nc.navigationBar.standardAppearance = navBarAppearance
+            nc.navigationBar.scrollEdgeAppearance = navBarAppearance
+            let backButton = navigationQLPreviewDocument(title: "Cancel".localized(), style: .plain, target: self, action: #selector(cancelDocumentPreview))
+            vcHandleFile.navigationItem.leftBarButtonItem = backButton
+            let sendButton = navigationQLPreviewDocument(title: "Send".localized(), style: .done, target: self, action: #selector(sendDocument))
+            buttonSpec.setImage(UIImage(named: "pb_ic_attach_spc_off", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal).resize(target: CGSize(width: 30, height: 30)), for: .normal)
+            buttonSpec.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
+            buttonSpec.addTarget(self, action: #selector(showConfigurationFile), for: .touchUpInside)
+            let barButtonItemSpec = UIBarButtonItem(customView: buttonSpec)
+            vcHandleFile.navigationItem.rightBarButtonItems = [sendButton, barButtonItemSpec]
+            backButton.navigation = nc
+            sendButton.navigation = nc
+            if let viewVc = vcHandleFile.view {
+                vcHandleFile.title = self.previewItem?.lastPathComponent
+                vcHandleFile.addChild(previewController)
+                previewController.dataSource = self
+                previewController.view.frame = CGRect(x: 0, y: 0, width: viewVc.bounds.size.width, height: viewVc.bounds.size.height)
+                previewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+                viewVc.addSubview(previewController.view)
+                previewController.didMove(toParent: vcHandleFile)
+                
+                self.present(nc, animated: true)
+            }
+        }
+    }
+    
+    @objc private func showConfigurationFile() {
+        let modalVC = UIViewController()
+        if let viewModal = modalVC.view {
+            viewModal.backgroundColor = .whiteBubbleColor
+            
+            let closeButton = UIButton(type: .close)
+            viewModal.addSubview(closeButton)
+            closeButton.anchor(top: viewModal.topAnchor, right: viewModal.rightAnchor, paddingTop: 15, paddingRight: 15, width: 30, height: 30)
+            closeButton.layer.cornerRadius = 15
+            closeButton.clipsToBounds = true
+            closeButton.backgroundColor = .lightGray.withAlphaComponent(0.1)
+            let config = UIImage.SymbolConfiguration(pointSize: 18, weight: .semibold)
+            closeButton.setImage(UIImage(systemName: "xmark", withConfiguration: config), for: .normal)
+            closeButton.addAction(UIAction { _ in
+                modalVC.dismiss(animated: true)
+            }, for: .touchUpInside)
             
-            self.present(navController, animated: true, completion: nil)
+            let imageSpec = UIButton(type: .custom)
+            viewModal.addSubview(imageSpec)
+            imageSpec.anchor(top: viewModal.topAnchor, left: viewModal.leftAnchor, paddingTop: 25, paddingLeft: 15, width: 40, height: 40)
+            imageSpec.layer.cornerRadius = 20
+            imageSpec.clipsToBounds = true
+            imageSpec.backgroundColor = .lightGray.withAlphaComponent(0.1)
+            imageSpec.setImage(UIImage(named: "pb_ic_attach_spc", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal).resize(target: CGSize(width: 35, height: 35)), for: .normal)
+            
+            let title = UILabel()
+            title.text = "Option for Attachment".localized()
+            viewModal.addSubview(title)
+            title.anchor(top: viewModal.topAnchor, left: imageSpec.rightAnchor, paddingTop: 23, paddingLeft: 10)
+            title.textColor = .label
+            title.font = .boldSystemFont(ofSize: 16)
+            
+            let subtitle = UILabel()
+            subtitle.text = "Select option :".localized()
+            viewModal.addSubview(subtitle)
+            subtitle.anchor(top: title.bottomAnchor, left: imageSpec.rightAnchor, paddingLeft: 10)
+            subtitle.textColor = .gray
+            subtitle.font = .systemFont(ofSize: 14)
+            
+            tableViewConfigFile = UITableView()
+            viewModal.addSubview(tableViewConfigFile)
+            tableViewConfigFile.backgroundColor = .white
+            tableViewConfigFile.layer.cornerRadius = 8.0
+            tableViewConfigFile.clipsToBounds = true
+            tableViewConfigFile.anchor(top: imageSpec.bottomAnchor, left: viewModal.leftAnchor, bottom: viewModal.bottomAnchor, right: viewModal.rightAnchor, paddingTop: 15, paddingLeft: 15, paddingBottom: 80, paddingRight: 15)
+            tableViewConfigFile.register(UITableViewCell.self, forCellReuseIdentifier: "cellConfigFile")
+            tableViewConfigFile.dataSource = self
+            tableViewConfigFile.delegate = self
+            tableViewConfigFile.separatorStyle = .singleLine
+            tableViewConfigFile.tableFooterView = UIView()
+            if #available(iOS 15.0, *) {
+                tableViewConfigFile.sectionHeaderTopPadding = 0
+            }
+            
+            if #available(iOS 15.0, *) {
+                if let sheet = modalVC.sheetPresentationController {
+                    sheet.detents = [.medium()]
+                }
+            } else {
+                // Fallback on earlier versions
+            }
         }
+        UIApplication.shared.visibleViewController?.present(modalVC, animated: true)
     }
     
     @objc private func cancelDocumentPreview(sender: navigationQLPreviewDocument) {
@@ -4665,13 +4765,12 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
                                                                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 ?? "")
+                                                               opposite_pin: dataMessages[indexPath!.row][TypeDataMessage.opposite_pin]  as? String ?? "", specFile: "")
             Nexilis.addQueueMessage(message: message)
         })
         
-        var children: [UIMenuElement] = [star, reply, forward, copy, delete]
+        var children: [UIMenuElement] = [star, reply, copy, delete]
         var isMore = false
-//        let copyOption = self.copyOption(indexPath: indexPath!)
         let idMe = User.getMyPin() as String?
         if dataMessages[indexPath!.row]["status"]  as? String ?? "" == "0" {
             children = [resend, delete]
@@ -4682,32 +4781,24 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
             else {
                 children = [reply, copy]
             }
-        } else if (dataMessages[indexPath!.row]["lock"] != nil && dataMessages[indexPath!.row]["lock"] as? String == "1") || dataMessages[indexPath!.row]["message_scope_id"] as? String == MessageScope.FORM || dataPerson["f_pin"] == "-999" || dataMessages[indexPath!.row]["credential"]  as? String == "1" || dataMessages[indexPath!.row]["message_scope_id"] as? String == MessageScope.CALL || dataMessages[indexPath!.row]["message_scope_id"] as? String == MessageScope.MISSED_CALL {
+        } else if (dataMessages[indexPath!.row]["lock"] != nil && dataMessages[indexPath!.row]["lock"] as? String == "1") || dataMessages[indexPath!.row]["message_scope_id"] as? String == MessageScope.FORM || dataPerson["f_pin"] == "-999" || dataMessages[indexPath!.row]["credential"]  as? String == "1" || dataMessages[indexPath!.row]["message_scope_id"] as? String == MessageScope.CALL || dataMessages[indexPath!.row]["message_scope_id"] as? String == MessageScope.MISSED_CALL || blocking == "1" || blocking == "-1" {
             children = [delete]
         } else if (groupImages[dataMessages[indexPath!.row]["message_id"]  as? String ?? ""] != nil) {
             forward.title = "Forward All".localized()
             delete.title = "Delete All".localized()
-            children = [forward, delete]
-        } else if blocking == "1" || blocking == "-1" {
-            children = [star, forward, copy ,delete]
-            if !(dataMessages[indexPath!.row]["image_id"]  as? String ?? "").isEmpty || !(dataMessages[indexPath!.row]["video_id"]  as? String ?? "").isEmpty {
-                children = [star, forward ,delete]
-            }
-            if (dataMessages[indexPath!.row]["f_pin"]  as? String ?? "") == idMe {
-                children.insert(info, at: children.count - 1)
+            children = [delete]
+            if Nexilis.checkingAccess(key: "secure_folder_forward") || (dataMessages[indexPath!.row][TypeDataMessage.spec_file] as? String ?? "").contains("forward") {
+                children.insert(forward, at: 0)
             }
-        }
-        else if !(dataMessages[indexPath!.row]["image_id"]  as? String ?? "").isEmpty || !(dataMessages[indexPath!.row]["video_id"]  as? String ?? "").isEmpty || !(dataMessages[indexPath!.row]["file_id"]  as? String ?? "").isEmpty {
-            children = [star, reply, forward ,delete]
-            if (dataMessages[indexPath!.row]["f_pin"]  as? String ?? "") == idMe {
-                children.insert(info, at: children.count - 1)
+        } else {
+            if !(dataMessages[indexPath!.row]["image_id"]  as? String ?? "").isEmpty || !(dataMessages[indexPath!.row]["video_id"]  as? String ?? "").isEmpty || !(dataMessages[indexPath!.row]["file_id"]  as? String ?? "").isEmpty {
+               children = [star, reply ,delete]
+            } else if dataMessages[indexPath!.row]["attachment_flag"]  as? String ?? "" == "11" {
+               children = [reply, delete]
             }
-        } else if dataMessages[indexPath!.row]["attachment_flag"]  as? String ?? "" == "11" {
-            children = [reply, delete]
-            if (dataMessages[indexPath!.row]["f_pin"]  as? String ?? "") == idMe {
-                children.insert(info, at: children.count - 1)
+            if (Nexilis.checkingAccess(key: "secure_folder_forward") && dataMessages[indexPath!.row]["attachment_flag"]  as? String ?? "" != "11") || (!(dataMessages[indexPath!.row][TypeDataMessage.message_text]  as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["image_id"]  as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["video_id"]  as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["file_id"]  as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["audio_id"]  as? String ?? "").isEmpty) || (dataMessages[indexPath!.row][TypeDataMessage.spec_file] as? String ?? "").contains("forward") {
+                children.insert(forward, at: 2)
             }
-        } else {
             if (dataMessages[indexPath!.row]["f_pin"]  as? String ?? "") == idMe {
                 children.insert(info, at: children.count - 1)
             }
@@ -5521,6 +5612,33 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
     }
     
     public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        if tableView == tableViewConfigFile {
+            tableView.deselectRow(at: indexPath, animated: true)
+            var type = ""
+            if indexPath.row == 0 {
+                type = "share,download"
+            } else {
+                type = "forward"
+            }
+            if !specFileString.contains(type) {
+                if !specFileString.isEmpty {
+                    specFileString += ","
+                }
+                specFileString += type
+            } else {
+                specFileString = specFileString.replacingOccurrences(of: type, with: "")
+                if specFileString == "," {
+                    specFileString = ""
+                }
+            }
+            if specFileString.isEmpty {
+                buttonSpec.setImage(UIImage(named: "pb_ic_attach_spc_off", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal).resize(target: CGSize(width: 30, height: 30)), for: .normal)
+            } else {
+                buttonSpec.setImage(UIImage(named: "pb_ic_attach_spc", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal).resize(target: CGSize(width: 30, height: 30)), for: .normal)
+            }
+            tableView.reloadData()
+            return
+        }
         if isContactCenter && indexPath.row == 0 && isRequestContactCenter {
             return
         }
@@ -5533,21 +5651,28 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                 return
             }
             
-            if !(dataMessages[indexPath.row]["image_id"]  as? String ?? "").isEmpty || !(dataMessages[indexPath.row]["video_id"]  as? String ?? "").isEmpty || !(dataMessages[indexPath.row]["file_id"]  as? String ?? "").isEmpty {
-                var file = dataMessages[indexPath.row]["image_id"]  as? String ?? ""
-                if file.isEmpty {
-                    file = dataMessages[indexPath.row]["video_id"]  as? String ?? ""
+            if !(dataMessages[indexPath.row]["image_id"]  as? String ?? "").isEmpty || !(dataMessages[indexPath.row]["video_id"]  as? String ?? "").isEmpty || !(dataMessages[indexPath.row]["file_id"]  as? String ?? "").isEmpty || !(dataMessages[indexPath.row]["audio_id"]  as? String ?? "").isEmpty {
+                if !Nexilis.checkingAccess(key: "secure_folder_forward") && !(dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String ?? "").contains("forward") {
+                    return
+                } else {
+                    var file = dataMessages[indexPath.row]["image_id"]  as? String ?? ""
                     if file.isEmpty {
-                        file = dataMessages[indexPath.row]["file_id"]  as? String ?? ""
+                        file = dataMessages[indexPath.row]["video_id"]  as? String ?? ""
+                        if file.isEmpty {
+                            file = dataMessages[indexPath.row]["file_id"]  as? String ?? ""
+                            if file.isEmpty {
+                                file = dataMessages[indexPath.row]["audio_id"]  as? String ?? ""
+                            }
+                        }
                     }
-                }
-                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(file)
-                    if !FileManager.default.fileExists(atPath: fileURL.path) && !FileEncryption.shared.isSecureExists(filename: fileURL.lastPathComponent) {
-                        return
+                    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(file)
+                        if !FileManager.default.fileExists(atPath: fileURL.path) && !FileEncryption.shared.isSecureExists(filename: fileURL.lastPathComponent) {
+                            return
+                        }
                     }
                 }
             }
@@ -5642,6 +5767,9 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
     }
     
     public func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
+        if tableView == tableViewConfigFile {
+            return nil
+        }
         let containerView = UIView()
         containerView.backgroundColor = .clear
         
@@ -5684,10 +5812,33 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
     }
     
     public func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
+        if tableView == tableViewConfigFile {
+            return 0
+        }
         return 50
     }
     
     public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        if tableView == tableViewConfigFile {
+            let cell = tableView.dequeueReusableCell(withIdentifier: "cellConfigFile", for: indexPath as IndexPath)
+            var content = cell.defaultContentConfiguration()
+            content.textProperties.font = .systemFont(ofSize: 16, weight: .medium)
+            content.textProperties.color = .label
+            content.secondaryTextProperties.font = .systemFont(ofSize: 14)
+            content.secondaryTextProperties.color = .gray
+            if indexPath.row == 0 {
+                content.text = "Can Share and Download".localized()
+                content.secondaryText = "The user, as the receiver, can share and download the attachment.".localized()
+                cell.accessoryType = specFileString.contains("share,download") ? .checkmark : .none
+            } else {
+                content.text = "Can Forward".localized()
+                content.secondaryText = "The user, as the receiver, can forward the attachment.".localized()
+                cell.accessoryType = specFileString.contains("forward") ? .checkmark : .none
+            }
+            cell.contentConfiguration = content
+            cell.tintColor = .black
+            return cell
+        }
         let idMe = User.getMyPin() as String?
         let dataMessages = dataMessages.filter({$0["chat_date"]  as? String ?? "" == dataDates[indexPath.section]})
         let profileMessage = UIImageView()
@@ -5958,20 +6109,27 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
         if (dataMessages[indexPath.row]["attachment_flag"] as? String == "0" && dataMessages[indexPath.row]["lock"] as? String != "1") || forwardSession || deleteSession {
             var showSelectedImage = true
             if (!imageChat.isEmpty || !videoChat.isEmpty || !fileChat.isEmpty) && forwardSession {
-                var file = dataMessages[indexPath.row]["image_id"]  as? String ?? ""
-                if file.isEmpty {
-                    file = dataMessages[indexPath.row]["video_id"]  as? String ?? ""
+                if !Nexilis.checkingAccess(key: "secure_folder_forward") && !(dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String ?? "").contains("forward") {
+                    showSelectedImage = false
+                } else {
+                    var file = dataMessages[indexPath.row]["image_id"]  as? String ?? ""
                     if file.isEmpty {
-                        file = dataMessages[indexPath.row]["file_id"]  as? String ?? ""
+                        file = dataMessages[indexPath.row]["video_id"]  as? String ?? ""
+                        if file.isEmpty {
+                            file = dataMessages[indexPath.row]["file_id"]  as? String ?? ""
+                            if file.isEmpty {
+                                file = dataMessages[indexPath.row]["audio_id"]  as? String ?? ""
+                            }
+                        }
                     }
-                }
-                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(file)
-                    if !FileManager.default.fileExists(atPath: fileURL.path) && !FileEncryption.shared.isSecureExists(filename: fileURL.lastPathComponent) {
-                        showSelectedImage = false
+                    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(file)
+                        if !FileManager.default.fileExists(atPath: fileURL.path) && !FileEncryption.shared.isSecureExists(filename: fileURL.lastPathComponent) {
+                            showSelectedImage = false
+                        }
                     }
                 }
             }
@@ -6139,8 +6297,9 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
             imageStared.tintColor = .systemYellow
         }
         
+        let imageAckView = UIImageView()
+        let imageCredentialView = UIImageView()
         if dataMessages[indexPath.row]["read_receipts"] as? String == "8" {
-            let imageAckView = UIImageView()
             var imageAck = UIImage(named: "ack_icon_gray", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
             if dataMessages[indexPath.row]["status"] as? String == "8" {
                 imageAck = UIImage(named: "ack_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
@@ -6150,11 +6309,10 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
             imageAckView.translatesAutoresizingMaskIntoConstraints = false
             imageAckView.widthAnchor.constraint(equalToConstant: 30).isActive = true
             imageAckView.heightAnchor.constraint(equalToConstant: 30).isActive = true
+            imageAckView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
             if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
-                imageAckView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
                 imageAckView.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 30).isActive = true
             } else {
-                imageAckView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
                 imageAckView.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -30).isActive = true
                 let tap = ObjectGesture(target: self, action: #selector(tapAck(_:)))
                 tap.indexPath = indexPath
@@ -6164,22 +6322,48 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
         }
         
         if (dataMessages[indexPath.row]["credential"] as? String) == "1" && (dataMessages[indexPath.row]["lock"] as? String) != "2" && (dataMessages[indexPath.row]["lock"] as? String) != "1" {
-            let imageCredentialView = UIImageView()
             let imageCredential = UIImage(named: "confidential_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
             imageCredentialView.image = imageCredential
             cell.contentView.addSubview(imageCredentialView)
             imageCredentialView.translatesAutoresizingMaskIntoConstraints = false
             imageCredentialView.widthAnchor.constraint(equalToConstant: 30).isActive = true
             imageCredentialView.heightAnchor.constraint(equalToConstant: 30).isActive = true
+            imageCredentialView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
             if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
-                imageCredentialView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
                 imageCredentialView.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 30).isActive = true
             } else {
-                imageCredentialView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
                 imageCredentialView.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -30).isActive = true
             }
         }
         
+        if !(dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String ?? "").isEmpty && (dataMessages[indexPath.row]["lock"] as? String) != "2" && (dataMessages[indexPath.row]["lock"] as? String) != "1" {
+            let imageSpecFileView = UIImageView()
+            let imageSpecFile = UIImage(named: "pb_ic_attach_spc", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
+            imageSpecFileView.image = imageSpecFile
+            cell.contentView.addSubview(imageSpecFileView)
+            imageSpecFileView.translatesAutoresizingMaskIntoConstraints = false
+            imageSpecFileView.widthAnchor.constraint(equalToConstant: 30).isActive = true
+            imageSpecFileView.heightAnchor.constraint(equalToConstant: 30).isActive = true
+            imageSpecFileView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
+            if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
+                if imageAckView.isDescendant(of: cell.contentView) {
+                    imageSpecFileView.leadingAnchor.constraint(equalTo: imageAckView.trailingAnchor, constant: 5).isActive = true
+                } else if imageCredentialView.isDescendant(of: cell.contentView) {
+                    imageSpecFileView.leadingAnchor.constraint(equalTo: imageCredentialView.trailingAnchor, constant: 5).isActive = true
+                } else {
+                    imageSpecFileView.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 30).isActive = true
+                }
+            } else {
+                if imageAckView.isDescendant(of: cell.contentView) {
+                    imageSpecFileView.trailingAnchor.constraint(equalTo: imageAckView.leadingAnchor, constant: -5).isActive = true
+                } else if imageCredentialView.isDescendant(of: cell.contentView) {
+                    imageSpecFileView.trailingAnchor.constraint(equalTo: imageCredentialView.leadingAnchor, constant: -5).isActive = true
+                } else {
+                    imageSpecFileView.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -30).isActive = true
+                }
+            }
+        }
+        
 //        if dataMessages[indexPath.row][TypeDataMessage.last_edit] != nil && dataMessages[indexPath.row][TypeDataMessage.last_edit] as! Int64 != 0 {
 //            let editedText = UILabel()
 //            editedText.text = "Edited".localized()
@@ -6996,12 +7180,14 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                 
                 if !copySession && !forwardSession && !deleteSession {
                     let objectTap = ObjectGesture(target: self, action: #selector(contentMessageTapped(_:)))
+                    let sfs = (dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String) ?? ""
                     imageThumb.isUserInteractionEnabled = true
                     imageThumb.addGestureRecognizer(objectTap)
                     objectTap.image_id = imageChat
                     objectTap.video_id = videoChat
                     objectTap.gif_id = gifChat
                     objectTap.imageView = imageThumb
+                    objectTap.specFile = sfs
                     objectTap.indexPath = indexPath
                 }
             }
@@ -7133,10 +7319,12 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
             }
             if !copySession && !forwardSession && !deleteSession {
                 let objectTap = ObjectGesture(target: self, action: #selector(contentMessageTapped(_:)))
+                let sfs = (dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String) ?? ""
                 containerViewFile.addGestureRecognizer(objectTap)
                 objectTap.containerFile = containerViewFile
                 objectTap.labelFile = nameFile
                 objectTap.file_id = fileChat
+                objectTap.specFile = sfs
                 objectTap.indexPath = indexPath
             }
         }
@@ -7788,10 +7976,16 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
 //    }
     
     public func numberOfSections(in tableView: UITableView) -> Int {
-        dataDates.count
+        if tableView == tableViewConfigFile {
+            return 1
+        }
+        return dataDates.count
     }
     
     public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        if tableView == tableViewConfigFile {
+            return 2
+        }
         let count = dataMessages.filter({ $0["chat_date"]  as? String ?? "" == dataDates[section] }).count
         return count
     }
@@ -7800,17 +7994,80 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
         let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
         let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
         let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
+        func showMedia(data: Data? = nil, url: URL? = nil, type: Int = 0) {
+            let image = UIImage(data: data ?? Data())
+            let imageViewer = MediaViewerViewController()
+            if type == 0 {
+                imageViewer.media = .image(image ?? UIImage())
+            } else if type == 1 {
+                imageViewer.media = .video(url ?? URL(string: "")!)
+            } else if type == 2 {
+                imageViewer.media = .gif(UIImage.gifImageWithData(data ?? Data()) ?? UIImage())
+            }
+            
+            let navigationController = UINavigationController(rootViewController: imageViewer)
+            navigationController.defaultStyle()
+            navigationController.view.backgroundColor = .clear
+            navigationController.modalPresentationCapturesStatusBarAppearance = true
+            navigationController.modalPresentationStyle = .overFullScreen
+            
+            let backAction = UIAction { _ in
+                navigationController.dismiss(animated: true)
+            }
+            let backButton = UIBarButtonItem(title: nil, image: UIImage(systemName: "chevron.backward"), primaryAction: backAction, menu: nil)
+            imageViewer.navigationItem.leftBarButtonItem = backButton
+            if Nexilis.checkingAccess(key: "secure_folder_share") || sender.specFile.contains("download") || sender.specFile.contains("share") {
+                let shareAction = UIAction { _ in
+                    var activityViewController = UIActivityViewController(activityItems: [image ?? UIImage()], applicationActivities: nil)
+                    if type == 1 {
+                        activityViewController = UIActivityViewController(activityItems: [url ?? URL(string: "")!], applicationActivities: nil)
+                    }
+                    activityViewController.popoverPresentationController?.sourceView = imageViewer.view
+                    imageViewer.present(activityViewController, animated: true, completion: nil)
+                }
+                let shareButton = UIBarButtonItem(title: nil, image: UIImage(systemName: "square.and.arrow.up"), primaryAction: shareAction, menu: nil)
+                imageViewer.navigationItem.rightBarButtonItem = shareButton
+            }
+            
+            var name = ""
+            if !isContactCenter {
+                name = dataPerson["name"] as? String ?? ""
+            } else {
+                if users.count == 1 {
+                    name = users[0].fullName
+                } else {
+                    var stringName = ""
+                    for user in users {
+                        if stringName.isEmpty {
+                            stringName = user.fullName
+                        } else {
+                            stringName += ", \(user.fullName)"
+                        }
+                    }
+                    name = stringName
+                }
+            }
+            imageViewer.title = name
+            
+            let transitionDelegate = ZoomTransitioningDelegate()
+            transitionDelegate.originImageView = sender.imageView
+            navigationController.transitioningDelegate = transitionDelegate
+            self.transitioningDelegateRef = transitionDelegate
+            
+            present(navigationController, animated: true) {
+                imageViewer.animateBackgroundIn()
+            }
+        }
         if (sender.image_id != "") {
             if let dirPath = paths.first {
                 let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.image_id)
                 if FileManager.default.fileExists(atPath: imageURL.path) {
-                    let image    = UIImage(contentsOfFile: imageURL.path)
-                    let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
-                    previewImageVC.image = image
-                    previewImageVC.isHiddenTextField = true
-                    previewImageVC.modalPresentationStyle = .custom
-                    previewImageVC.modalTransitionStyle  = .crossDissolve
-                    self.present(previewImageVC, animated: true, completion: nil)
+                    self.previewItem = imageURL as NSURL
+                    do {
+                        showMedia(data: try Data(contentsOf: imageURL))
+                    } catch {
+                        
+                    }
                 } else if FileEncryption.shared.isSecureExists(filename: sender.image_id) {
                     do {
                         if var data = try FileEncryption.shared.readSecure(filename: sender.image_id) {
@@ -7818,13 +8075,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                             if dataDecrypt != nil {
                                 data = dataDecrypt!
                             }
-                            let image = UIImage(data: data)
-                            let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
-                            previewImageVC.image = image
-                            previewImageVC.isHiddenTextField = true
-                            previewImageVC.modalPresentationStyle = .custom
-                            previewImageVC.modalTransitionStyle  = .crossDissolve
-                            self.present(previewImageVC, animated: true, completion: nil)
+                            showMedia(data: data)
                         }
                     }
                     catch {
@@ -7848,32 +8099,6 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                             return
                         }
                         do {
-                            let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
-                            let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
-                            let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
-                            if let dirPath = paths.first {
-                                let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.image_id)
-                                if FileManager.default.fileExists(atPath: imageURL.path) {
-                                    let image    = UIImage(contentsOfFile: imageURL.path)
-                                    let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
-                                    if save {
-                                        UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
-                                    }
-                                }
-                                else if FileEncryption.shared.isSecureExists(filename: sender.image_id) {
-                                    if var secureData = try FileEncryption.shared.readSecure(filename: sender.image_id) {
-                                        let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: secureData)
-                                        if dataDecrypt != nil {
-                                            secureData = dataDecrypt!
-                                        }
-                                        let image = UIImage(data: secureData)
-                                        let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
-                                        if save {
-                                            UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
-                                        }
-                                    }
-                                }
-                            }
                             DispatchQueue.main.async {
                                 activityIndicator.stopAnimating()
                                 self.tableChatView.reloadRows(at: [sender.indexPath], with: .none)
@@ -7890,7 +8115,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                 if FileManager.default.fileExists(atPath: gifURL.path) {
                     do {
                         let data = try Data(contentsOf: gifURL)
-                        APIS.openImageNexilis(image: UIImage(), data: data, isGIF: true)
+                        showMedia(data: data, type: 2)
                     } catch {
                         
                     }
@@ -7901,7 +8126,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                             if dataDecrypt != nil {
                                 secureData = dataDecrypt!
                             }
-                            APIS.openImageNexilis(image: UIImage(), data: secureData, isGIF: true)
+                            showMedia(data: secureData, type: 2)
                         }
                     } catch {
                         
@@ -7912,11 +8137,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
             if let dirPath = paths.first {
                 let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.video_id)
                 if FileManager.default.fileExists(atPath: videoURL.path) {
-                    let player = AVPlayer(url: videoURL as URL)
-                    let playerVC = AVPlayerViewController()
-                    playerVC.modalPresentationStyle = .custom
-                    playerVC.player = player
-                    self.present(playerVC, animated: true, completion: nil)
+                    showMedia(url: videoURL, type: 1)
                 } else if FileEncryption.shared.isSecureExists(filename: sender.video_id) {
                     do {
                         if var secureData = try FileEncryption.shared.readSecure(filename: sender.video_id) {
@@ -7927,11 +8148,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                             let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
                             let tempPath = cachesDirectory.appendingPathComponent(sender.video_id)
                             try secureData.write(to: tempPath)
-                            let player = AVPlayer(url: tempPath as URL)
-                            let playerVC = AVPlayerViewController()
-                            playerVC.modalPresentationStyle = .custom
-                            playerVC.player = player
-                            self.present(playerVC, animated: true, completion: nil)
+                            showMedia(url: tempPath, type: 1)
                         }
                     } catch {
                         
@@ -7981,27 +8198,6 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                             guard progress == 100 else {
                                 shapeLoading.strokeEnd = CGFloat(progress / 100)
                                 return
-                            }
-                            do {
-                                if var secureData = try FileEncryption.shared.readSecure(filename: sender.video_id) {
-                                    let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: secureData)
-                                    if dataDecrypt != nil {
-                                        secureData = dataDecrypt!
-                                    }
-                                    let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
-                                    let tempPath = cachesDirectory.appendingPathComponent(name)
-                                    try secureData.write(to: tempPath)
-                                    let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
-                                    if save {
-                                        PHPhotoLibrary.shared().performChanges({
-                                            PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: tempPath)
-                                        }) { saved, error in
-                                            
-                                        }
-                                    }
-                                }
-                            } catch {
-                                
                             }
                             let idx = self.dataMessages.firstIndex(where: { $0["video_id"]  as? String ?? "" == sender.video_id})
                             if idx != nil {
@@ -8013,17 +8209,59 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                 }
             }
         } else if (sender.file_id != "") {
+            func showFile(urlFile: URL, isFile: Bool = true) {
+                let previewController = QLPreviewController()
+                previewController.dataSource = self
+                let vcHandleFile = UIViewController()
+                let nc = UINavigationController(rootViewController: vcHandleFile)
+                let attributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
+                let navBarAppearance = UINavigationBarAppearance()
+                nc.defaultStyle()
+                navBarAppearance.configureWithOpaqueBackground()
+                navBarAppearance.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : UIColor.mainColor
+                navBarAppearance.titleTextAttributes = attributes
+                nc.navigationBar.standardAppearance = navBarAppearance
+                nc.navigationBar.scrollEdgeAppearance = navBarAppearance
+                let backAction = UIAction { _ in
+                    nc.dismiss(animated: true)
+                }
+                let backButton = UIBarButtonItem(title: nil, image: UIImage(systemName: "chevron.backward"), primaryAction: backAction, menu: nil)
+                vcHandleFile.navigationItem.leftBarButtonItem = backButton
+                if Nexilis.checkingAccess(key: "secure_folder_share") || sender.specFile.contains("download") || sender.specFile.contains("share") {
+                    let shareAction = UIAction { _ in
+                        let fileManager = FileManager.default
+                        let tempURL = fileManager.temporaryDirectory.appendingPathComponent(sender.labelFile.text ?? "")
+                        do {
+                            try fileManager.copyItem(at: urlFile, to: tempURL)
+                            let activityViewController = UIActivityViewController(activityItems: [tempURL], applicationActivities: nil)
+                            activityViewController.popoverPresentationController?.sourceView = vcHandleFile.view
+                            vcHandleFile.present(activityViewController, animated: true, completion: nil)
+                        } catch {
+                            
+                        }
+                    }
+                    let shareButton = UIBarButtonItem(title: nil, image: UIImage(systemName: "square.and.arrow.up"), primaryAction: shareAction, menu: nil)
+                    vcHandleFile.navigationItem.rightBarButtonItem = shareButton
+                }
+                if let viewVc = vcHandleFile.view {
+                    if isFile {
+                        vcHandleFile.title = sender.labelFile.text
+                    }
+                    vcHandleFile.addChild(previewController)
+                    previewController.dataSource = self
+                    previewController.view.frame = CGRect(x: 0, y: 0, width: viewVc.bounds.size.width, height: viewVc.bounds.size.height)
+                    previewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+                    viewVc.addSubview(previewController.view)
+                    previewController.didMove(toParent: vcHandleFile)
+                    
+                    self.present(nc, animated: 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 = CustomQLPreviewController()
-//                    let rightBarButton = UIBarButtonItem()
-//                    previewController.navigationItem.rightBarButtonItem = rightBarButton
-                    previewController.dataSource = self
-//                    previewController.modalPresentationStyle = .custom
-                    
-                    self.present(previewController, animated: true)
+                    showFile(urlFile: fileURL)
                 } else if FileEncryption.shared.isSecureExists(filename: sender.file_id) {
                     do {
                         if var docData = try FileEncryption.shared.readSecure(filename: sender.file_id) {
@@ -8035,12 +8273,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                             let tempPath = cachesDirectory.appendingPathComponent(sender.file_id)
                             try docData.write(to: tempPath)
                             self.previewItem = tempPath as NSURL
-                            let previewController = CustomQLPreviewController()
-//                            let rightBarButton = UIBarButtonItem()
-//                            previewController.navigationItem.rightBarButtonItem = rightBarButton
-                            previewController.dataSource = self
-//                            previewController.modalPresentationStyle = .custom
-                            self.present(previewController,animated: true)
+                            showFile(urlFile: tempPath)
                         }
                     }
                     catch {
@@ -8548,6 +8781,7 @@ public class ObjectGesture: UITapGestureRecognizer {
     public var file_id = ""
     public var audio_id = ""
     public var gif_id = ""
+    public var specFile = ""
     public var imageView = UIImageView()
     public var containerFile = UIView()
     public var labelFile = UILabel()
@@ -8623,21 +8857,5 @@ public class TypeDataMessage {
     public static let gif_id = "gif_id"
     public static let is_forwarded = "is_forwarded"
     public static let is_secret = "is_secret"
-}
-
-public final class CustomQLPreviewController: QLPreviewController {
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        navigationItem.rightBarButtonItem = UIBarButtonItem()
-    }
-
-    public override func viewDidAppear(_ animated: Bool) {
-        super.viewDidAppear(animated)
-        (children[0] as? UINavigationController)?.setToolbarHidden(true, animated: false)
-    }
-
-    public override func viewDidLayoutSubviews() {
-        super.viewDidLayoutSubviews()
-        (children[0] as? UINavigationController)?.setToolbarHidden(true, animated: false)
-    }
+    public static let spec_file = "spec_file"
 }

+ 240 - 101
NexilisLite/NexilisLite/Source/View/Chat/EditorStarMessages.swift

@@ -11,6 +11,8 @@ import AVFoundation
 import QuickLook
 import Photos
 import SwiftLinkPreview
+import nuSDKService
+import NotificationBannerSwift
 
 public class EditorStarMessages: UIViewController, UITableViewDataSource, UITableViewDelegate, UIContextMenuInteractionDelegate, QLPreviewControllerDataSource, UITextViewDelegate {
     @IBOutlet var tableChatView: UITableView!
@@ -23,6 +25,9 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
     var touchedSubview = UIView()
     var lastTouchPoint: CGPoint = .zero
     
+    var downloadList: [String: IndexPath] = [:]
+    var transitioningDelegateRef: ZoomTransitioningDelegate?
+    
     func offset() -> CGFloat{
         guard let fontSize = Int(SecureUserDefaults.shared.value(forKey: "font_size") ?? "0") else { return 0 }
         return CGFloat(fontSize)
@@ -536,10 +541,12 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
             }
             
             let objectTap = ObjectGesture(target: self, action: #selector(contentMessageTapped(_:)))
+            let sfs = (dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String) ?? ""
             imageThumb.isUserInteractionEnabled = true
             imageThumb.addGestureRecognizer(objectTap)
             objectTap.image_id = imageChat
             objectTap.video_id = videoChat
+            objectTap.specFile = sfs
             objectTap.imageView = imageThumb
             objectTap.indexPath = indexPath
         }
@@ -666,10 +673,12 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
                 nameFile.trailingAnchor.constraint(equalTo: containerViewFile.trailingAnchor, constant: -5).isActive = true
             }
             let objectTap = ObjectGesture(target: self, action: #selector(contentMessageTapped(_:)))
+            let sfs = (dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String) ?? ""
             containerViewFile.addGestureRecognizer(objectTap)
             objectTap.containerFile = containerViewFile
             objectTap.labelFile = nameFile
             objectTap.file_id = fileChat
+            objectTap.specFile = sfs
             objectTap.indexPath = indexPath
         }
         
@@ -833,8 +842,96 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
             }
         }
         
+        let imageAckView = UIImageView()
+        if dataMessages[indexPath.row]["read_receipts"] as? String == "8" {
+            var imageAck = UIImage(named: "ack_icon_gray", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
+            if dataMessages[indexPath.row]["status"] as? String == "8" {
+                imageAck = UIImage(named: "ack_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
+            }
+            imageAckView.image = imageAck
+            cellMessage.contentView.addSubview(imageAckView)
+            imageAckView.translatesAutoresizingMaskIntoConstraints = false
+            imageAckView.widthAnchor.constraint(equalToConstant: 30).isActive = true
+            imageAckView.heightAnchor.constraint(equalToConstant: 30).isActive = true
+            imageAckView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
+            if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
+                imageAckView.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 30).isActive = true
+            } else {
+                imageAckView.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -30).isActive = true
+                let tap = ObjectGesture(target: self, action: #selector(tapAck(_:)))
+                tap.indexPath = indexPath
+                imageAckView.addGestureRecognizer(tap)
+                imageAckView.isUserInteractionEnabled = true
+            }
+        }
+        
+        if !(dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String ?? "").isEmpty && (dataMessages[indexPath.row]["lock"] as? String) != "2" && (dataMessages[indexPath.row]["lock"] as? String) != "1" {
+            let imageSpecFileView = UIImageView()
+            let imageSpecFile = UIImage(named: "pb_ic_attach_spc", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
+            imageSpecFileView.image = imageSpecFile
+            cellMessage.contentView.addSubview(imageSpecFileView)
+            imageSpecFileView.translatesAutoresizingMaskIntoConstraints = false
+            imageSpecFileView.widthAnchor.constraint(equalToConstant: 30).isActive = true
+            imageSpecFileView.heightAnchor.constraint(equalToConstant: 30).isActive = true
+            imageSpecFileView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
+            if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
+                if imageAckView.isDescendant(of: cellMessage.contentView) {
+                    imageSpecFileView.leadingAnchor.constraint(equalTo: imageAckView.trailingAnchor, constant: 5).isActive = true
+                } else {
+                    imageSpecFileView.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 30).isActive = true
+                }
+            } else {
+                if imageAckView.isDescendant(of: cellMessage.contentView) {
+                    imageSpecFileView.trailingAnchor.constraint(equalTo: imageAckView.leadingAnchor, constant: -5).isActive = true
+                } else {
+                    imageSpecFileView.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -30).isActive = true
+                }
+            }
+        }
+        
         return cellMessage
     }
+    
+    @objc func tapAck(_ sender: ObjectGesture) {
+        let indexPath = sender.indexPath
+        let dataMessages = self.dataMessages.filter({ $0["chat_date"]  as? String ?? "" == dataDates[indexPath.section]})
+        if dataMessages[indexPath.row]["status"]  as? String ?? "" == "8" {
+            return
+        }
+        if !CheckConnection.isConnectedToNetwork()  || API.nGetCLXConnState() == 0 {
+            let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
+            imageView.tintColor = .white
+            let banner = FloatingNotificationBanner(title: "Check your connection".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
+            banner.show()
+            return
+        }
+        DispatchQueue.global().async {
+            let result = Nexilis.write(message: CoreMessage_TMessageBank.getAckLocationMessage(f_pin: dataMessages[indexPath.row]["f_pin"]  as? String ?? "", message_id: dataMessages[indexPath.row]["message_id"]  as? String ?? "", l_pin: dataMessages[indexPath.row]["l_pin"]  as? String ?? "", server_date: "\(Date().currentTimeMillis())", message_scope_id: dataMessages[indexPath.row]["message_scope_id"]  as? String ?? "", longitude: "", latitude: "", description: ""))
+            if result != nil {
+                Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                    do {
+                        _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
+                            "status" : "8"
+                        ], _where: "message_id = '\(dataMessages[indexPath.row]["message_id"]  as? String ?? "")'")
+                    } catch {
+                        rollback.pointee = true
+                        print("Access database error: \(error.localizedDescription)")
+                    }
+                })
+                DispatchQueue.main.async {
+                    if let index = self.dataMessages.firstIndex(where: {$0["message_id"] as? String == dataMessages[indexPath.row]["message_id"] as? String}) {
+                        self.dataMessages[index]["status"] = "8"
+                        let section = self.dataDates.firstIndex(of: self.dataMessages[index]["chat_date"]  as? String ?? "")
+                        let row = self.dataMessages.filter({ $0["chat_date"]  as? String ?? "" == self.dataDates[section!]}).firstIndex(where: { $0["message_id"]  as? String ?? "" == self.dataMessages[index]["message_id"]  as? String ?? ""})
+                        if row != nil && section != nil {
+                            self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
+                        }
+                        self.view.makeToast("Confirmation Success.".localized(), duration: 3)
+                    }
+                }
+            }
+        }
+    }
 
     func highlightedText(for text: String, in range: Range<String.Index>, textView: UITextView) -> NSAttributedString {
         let mutableAttributedString = textView.attributedText!.mutableCopy() as! NSMutableAttributedString
@@ -867,7 +964,7 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
         }
         Database.shared.database?.inTransaction({ (fmdb, rollback) in
             do {
-                if let cursorData = Database.shared.getRecords(fmdb: fmdb, 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 FROM MESSAGE where is_stared=1 order by server_date asc") {
+                if let cursorData = Database.shared.getRecords(fmdb: fmdb, 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, attachment_speciality FROM MESSAGE where is_stared=1 order by server_date asc") {
                     while cursorData.next() {
                         var row: [String: Any?] = [:]
                         row["message_id"] = cursorData.string(forColumnIndex: 0)
@@ -889,6 +986,7 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
                         row["lock"] = cursorData.string(forColumnIndex: 16)
                         row["is_stared"] = cursorData.string(forColumnIndex: 17)
                         row["blog_id"] = cursorData.string(forColumnIndex: 18)
+                        row[TypeDataMessage.spec_file] = cursorData.string(forColumnIndex: 19)
                         if let cursorStatus = Database.shared.getRecords(fmdb: fmdb, query: "SELECT status FROM MESSAGE_STATUS WHERE message_id='\(row["message_id"] as! String)'") {
                             while cursorStatus.next() {
                                 row["status"] = cursorStatus.string(forColumnIndex: 0)
@@ -976,17 +1074,62 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
         let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
         let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
         let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
+        func showMedia(data: Data? = nil, url: URL? = nil, type: Int = 0) {
+            let image = UIImage(data: data ?? Data())
+            let imageViewer = MediaViewerViewController()
+            if type == 0 {
+                imageViewer.media = .image(image ?? UIImage())
+            } else if type == 1 {
+                imageViewer.media = .video(url ?? URL(string: "")!)
+            } else if type == 2 {
+                imageViewer.media = .gif(UIImage.gifImageWithData(data ?? Data()) ?? UIImage())
+            }
+            
+            let navigationController = UINavigationController(rootViewController: imageViewer)
+            navigationController.defaultStyle()
+            navigationController.view.backgroundColor = .clear
+            navigationController.modalPresentationCapturesStatusBarAppearance = true
+            navigationController.modalPresentationStyle = .overFullScreen
+            
+            let backAction = UIAction { _ in
+                navigationController.dismiss(animated: true)
+            }
+            let backButton = UIBarButtonItem(title: nil, image: UIImage(systemName: "chevron.backward"), primaryAction: backAction, menu: nil)
+            imageViewer.navigationItem.leftBarButtonItem = backButton
+            if Nexilis.checkingAccess(key: "secure_folder_share") || sender.specFile.contains("download") || sender.specFile.contains("share") {
+                let shareAction = UIAction { _ in
+                    var activityViewController = UIActivityViewController(activityItems: [image ?? UIImage()], applicationActivities: nil)
+                    if type == 1 {
+                        activityViewController = UIActivityViewController(activityItems: [url ?? URL(string: "")!], applicationActivities: nil)
+                    }
+                    activityViewController.popoverPresentationController?.sourceView = imageViewer.view
+                    imageViewer.present(activityViewController, animated: true, completion: nil)
+                }
+                let shareButton = UIBarButtonItem(title: nil, image: UIImage(systemName: "square.and.arrow.up"), primaryAction: shareAction, menu: nil)
+                imageViewer.navigationItem.rightBarButtonItem = shareButton
+            }
+            
+            let name = "Favorite Messages".localized()
+            imageViewer.title = name
+            
+            let transitionDelegate = ZoomTransitioningDelegate()
+            transitionDelegate.originImageView = sender.imageView
+            navigationController.transitioningDelegate = transitionDelegate
+            self.transitioningDelegateRef = transitionDelegate
+            
+            present(navigationController, animated: true) {
+                imageViewer.animateBackgroundIn()
+            }
+        }
         if (sender.image_id != "") {
             if let dirPath = paths.first {
                 let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.image_id)
                 if FileManager.default.fileExists(atPath: imageURL.path) {
-                    let image    = UIImage(contentsOfFile: imageURL.path)
-                    let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
-                    previewImageVC.image = image
-                    previewImageVC.isHiddenTextField = true
-                    previewImageVC.modalPresentationStyle = .custom
-                    previewImageVC.modalTransitionStyle  = .crossDissolve
-                    self.present(previewImageVC, animated: true, completion: nil)
+                    do {
+                        showMedia(data: try Data(contentsOf: imageURL))
+                    } catch {
+                        
+                    }
                 } else if FileEncryption.shared.isSecureExists(filename: sender.image_id) {
                     do {
                         if var data = try FileEncryption.shared.readSecure(filename: sender.image_id) {
@@ -994,13 +1137,7 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
                             if dataDecrypt != nil {
                                 data = dataDecrypt!
                             }
-                            let image = UIImage(data: data)
-                            let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
-                            previewImageVC.image = image
-                            previewImageVC.isHiddenTextField = true
-                            previewImageVC.modalPresentationStyle = .custom
-                            previewImageVC.modalTransitionStyle  = .crossDissolve
-                            self.present(previewImageVC, animated: true, completion: nil)
+                            showMedia(data: data)
                         }
                     }
                     catch {
@@ -1024,37 +1161,6 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
                             return
                         }
                         
-                        do {
-                            let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
-                            let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
-                            let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
-                            if let dirPath = paths.first {
-                                let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.image_id)
-                                if FileManager.default.fileExists(atPath: imageURL.path) {
-                                    let image    = UIImage(contentsOfFile: imageURL.path)
-                                    let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
-                                    if save {
-                                        UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
-                                    }
-                                }
-                                else if FileEncryption.shared.isSecureExists(filename: sender.image_id) {
-                                    if var secureData = try FileEncryption.shared.readSecure(filename: sender.image_id) {
-                                        let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: secureData)
-                                        if dataDecrypt != nil {
-                                            secureData = dataDecrypt!
-                                        }
-                                        let image = UIImage(data: secureData)
-                                        let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
-                                        if save {
-                                            UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
-                                        }
-                                    }
-                                }
-                            }
-                        } catch {
-                            
-                        }
-                        
                         DispatchQueue.main.async {
                             activityIndicator.stopAnimating()
                             self.tableChatView.reloadData()
@@ -1062,15 +1168,35 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
                     }
                 }
             }
+        } else if (sender.gif_id != "") {
+            if let dirPath = paths.first {
+                let gifURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.gif_id)
+                if FileManager.default.fileExists(atPath: gifURL.path) {
+                    do {
+                        let data = try Data(contentsOf: gifURL)
+                        showMedia(data: data, type: 2)
+                    } catch {
+                        
+                    }
+                } else if FileEncryption.shared.isSecureExists(filename: sender.gif_id) {
+                    do {
+                        if var secureData = try FileEncryption.shared.readSecure(filename: sender.gif_id) {
+                            let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: secureData)
+                            if dataDecrypt != nil {
+                                secureData = dataDecrypt!
+                            }
+                            showMedia(data: secureData, type: 2)
+                        }
+                    } catch {
+                        
+                    }
+                }
+            }
         } else if (sender.video_id != "") {
             if let dirPath = paths.first {
                 let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.video_id)
                 if FileManager.default.fileExists(atPath: videoURL.path) {
-                    let player = AVPlayer(url: videoURL as URL)
-                    let playerVC = AVPlayerViewController()
-                    playerVC.modalPresentationStyle = .custom
-                    playerVC.player = player
-                    self.present(playerVC, animated: true, completion: nil)
+                    showMedia(url: videoURL, type: 1)
                 } else if FileEncryption.shared.isSecureExists(filename: sender.video_id) {
                     do {
                         if var secureData = try FileEncryption.shared.readSecure(filename: sender.video_id) {
@@ -1081,11 +1207,7 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
                             let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
                             let tempPath = cachesDirectory.appendingPathComponent(sender.video_id)
                             try secureData.write(to: tempPath)
-                            let player = AVPlayer(url: tempPath as URL)
-                            let playerVC = AVPlayerViewController()
-                            playerVC.modalPresentationStyle = .custom
-                            playerVC.player = player
-                            self.present(playerVC, animated: true, completion: nil)
+                            showMedia(url: tempPath, type: 1)
                         }
                     } catch {
                         
@@ -1131,27 +1253,6 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
                             guard progress == 100 else {
                                 shapeLoading.strokeEnd = CGFloat(progress / 100)
                                 return
-                            }
-                            do {
-                                if var secureData = try FileEncryption.shared.readSecure(filename: sender.video_id) {
-                                    let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: secureData)
-                                    if dataDecrypt != nil {
-                                        secureData = dataDecrypt!
-                                    }
-                                    let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
-                                    let tempPath = cachesDirectory.appendingPathComponent(name)
-                                    try secureData.write(to: tempPath)
-                                    let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
-                                    if save {
-                                        PHPhotoLibrary.shared().performChanges({
-                                            PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: tempPath)
-                                        }) { saved, error in
-                                            
-                                        }
-                                    }
-                                }
-                            } catch {
-                                
                             }
                             let idx = self.dataMessages.firstIndex(where: { $0["video_id"] as! String == sender.video_id})
                             if idx != nil {
@@ -1163,17 +1264,57 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
                 }
             }
         } else if (sender.file_id != "") {
+            func showFile(urlFile: URL) {
+                let previewController = QLPreviewController()
+                previewController.dataSource = self
+                let vcHandleFile = UIViewController()
+                let nc = UINavigationController(rootViewController: vcHandleFile)
+                let attributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
+                let navBarAppearance = UINavigationBarAppearance()
+                nc.defaultStyle()
+                navBarAppearance.configureWithOpaqueBackground()
+                navBarAppearance.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : UIColor.mainColor
+                navBarAppearance.titleTextAttributes = attributes
+                nc.navigationBar.standardAppearance = navBarAppearance
+                nc.navigationBar.scrollEdgeAppearance = navBarAppearance
+                let backAction = UIAction { _ in
+                    nc.dismiss(animated: true)
+                }
+                let backButton = UIBarButtonItem(title: nil, image: UIImage(systemName: "chevron.backward"), primaryAction: backAction, menu: nil)
+                vcHandleFile.navigationItem.leftBarButtonItem = backButton
+                if Nexilis.checkingAccess(key: "secure_folder_share") || sender.specFile.contains("download") || sender.specFile.contains("share") {
+                    let shareAction = UIAction { _ in
+                        let fileManager = FileManager.default
+                        let tempURL = fileManager.temporaryDirectory.appendingPathComponent(sender.labelFile.text ?? "")
+                        do {
+                            try fileManager.copyItem(at: urlFile, to: tempURL)
+                            let activityViewController = UIActivityViewController(activityItems: [tempURL], applicationActivities: nil)
+                            activityViewController.popoverPresentationController?.sourceView = vcHandleFile.view
+                            vcHandleFile.present(activityViewController, animated: true, completion: nil)
+                        } catch {
+                            
+                        }
+                    }
+                    let shareButton = UIBarButtonItem(title: nil, image: UIImage(systemName: "square.and.arrow.up"), primaryAction: shareAction, menu: nil)
+                    vcHandleFile.navigationItem.rightBarButtonItem = shareButton
+                }
+                if let viewVc = vcHandleFile.view {
+                    vcHandleFile.title = sender.labelFile.text
+                    vcHandleFile.addChild(previewController)
+                    previewController.dataSource = self
+                    previewController.view.frame = CGRect(x: 0, y: 0, width: viewVc.bounds.size.width, height: viewVc.bounds.size.height)
+                    previewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+                    viewVc.addSubview(previewController.view)
+                    previewController.didMove(toParent: vcHandleFile)
+                    
+                    self.present(nc, animated: 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)
+                    showFile(urlFile: fileURL)
                 } else if FileEncryption.shared.isSecureExists(filename: sender.file_id) {
                     do {
                         if var docData = try FileEncryption.shared.readSecure(filename: sender.file_id) {
@@ -1185,18 +1326,17 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
                             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
+                            showFile(urlFile: tempPath)
                         }
-                    } catch {
+                    }
+                    catch {
                         
                     }
-                    
-                    
                 } else {
+                    if downloadList[sender.file_id] != nil && downloadList[sender.file_id] == sender.indexPath {
+                        return
+                    }
+                    downloadList[sender.file_id] = sender.indexPath
                     for view in sender.containerFile.subviews {
                         if !(view is UIImageView) && !(view is UILabel) {
                             view.removeFromSuperview()
@@ -1215,14 +1355,14 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
                     trackShape.path = circlePath.cgPath
                     trackShape.fillColor = UIColor.clear.cgColor
                     trackShape.lineWidth = 5
-                    trackShape.strokeColor = UIColor.mainColor.withAlphaComponent(0.3).cgColor
+                    trackShape.strokeColor = UIColor.mentionColor.withAlphaComponent(0.3).cgColor
                     containerLoading.layer.addSublayer(trackShape)
                     let shapeLoading = CAShapeLayer()
                     shapeLoading.path = circlePath.cgPath
                     shapeLoading.fillColor = UIColor.clear.cgColor
                     shapeLoading.lineWidth = 3
                     shapeLoading.strokeEnd = 0
-                    shapeLoading.strokeColor = UIColor.mainColor.cgColor
+                    shapeLoading.strokeColor = UIColor.mentionColor.cgColor
                     containerLoading.layer.addSublayer(shapeLoading)
                     let imageupload = UIImageView(image: UIImage(systemName: "arrow.down", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
                     imageupload.tintColor = .white
@@ -1237,7 +1377,7 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
                                 shapeLoading.strokeEnd = CGFloat(progress / 100)
                                 return
                             }
-                            let idx = self.dataMessages.firstIndex(where: { $0["file_id"] as! String == sender.file_id})
+                            let idx = self.dataMessages.firstIndex(where: { $0["file_id"]  as? String ?? "" == sender.file_id})
                             if idx != nil {
                                 self.dataMessages[idx!]["progress"] = progress
                                 self.tableChatView.reloadRows(at: [sender.indexPath], with: .none)
@@ -1446,13 +1586,12 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
             }
         })
         
-        var children: [UIMenuElement] = [star, forward, copy]
-//        let copyOption = self.copyOption(indexPath: indexPath!)
-        
-        if self.dataMessages[indexPath!.row]["f_pin"] as! String == "-999" {
+        var children: [UIMenuElement] = [star, copy]
+        if self.dataMessages[indexPath!.row]["f_pin"] as! String == "-999" || !(dataMessages[indexPath!.row]["image_id"] as! String).isEmpty || !(dataMessages[indexPath!.row]["video_id"] as! String).isEmpty || !(dataMessages[indexPath!.row]["file_id"] as! String).isEmpty || dataMessages[indexPath!.row]["attachment_flag"] as! String == "11" {
             children = [star]
-        } else if !(dataMessages[indexPath!.row]["image_id"] as! String).isEmpty || !(dataMessages[indexPath!.row]["video_id"] as! String).isEmpty || !(dataMessages[indexPath!.row]["file_id"] as! String).isEmpty || dataMessages[indexPath!.row]["attachment_flag"] as! String == "11" {
-            children = [star, forward]
+        }
+        if (Nexilis.checkingAccess(key: "secure_folder_forward") || (dataMessages[indexPath!.row][TypeDataMessage.spec_file] as? String ?? "").contains("forward")) && self.dataMessages[indexPath!.row]["f_pin"] as! String != "-999" {
+            children.insert(forward, at: 1)
         }
         
         return UIContextMenuConfiguration(identifier: nil,

+ 165 - 44
NexilisLite/NexilisLite/Source/View/Chat/PreviewAttachmentImageVideo.swift

@@ -11,7 +11,7 @@ import AVFoundation
 import SDWebImage
 
 protocol PreviewAttachmentImageVideoDelegate : NSObjectProtocol {
-    func sendChatFromPreviewImage(message_text: String, attachment_flag: String, image_id: String, video_id: String, thumb_id: String, gif_id: String, viewController: UIViewController)
+    func sendChatFromPreviewImage(message_text: String, attachment_flag: String, image_id: String, video_id: String, thumb_id: String, gif_id: String, viewController: UIViewController, specFile: String)
 }
 
 class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITextViewDelegate {
@@ -19,6 +19,7 @@ class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITex
     @IBOutlet var buttonSend: UIButton!
     @IBOutlet var textFieldSend: UITextView!
     @IBOutlet var buttonCancel: UIButton!
+    @IBOutlet weak var buttonSpecFile: UIButton!
     @IBOutlet var constraintViewTextField: NSLayoutConstraint!
     @IBOutlet var heightTextFieldSend: NSLayoutConstraint!
     @IBOutlet var constraintButtonSend: NSLayoutConstraint!
@@ -40,6 +41,8 @@ class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITex
     var isGroup = false
     var isCC = false
     var isGIF = false
+    var tableViewConfigFile: UITableView!
+    var specFileString = ""
     
     override func viewDidLoad() {
         super.viewDidLoad()
@@ -162,6 +165,10 @@ class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITex
         buttonCancel.circle()
         buttonCancel.backgroundColor = .secondaryColor.withAlphaComponent(0.4)
         buttonCancel.addTarget(self, action: #selector(cancelTapped), for: .touchUpInside)
+        
+        buttonSpecFile.circle()
+        buttonSpecFile.backgroundColor = .secondaryColor.withAlphaComponent(0.4)
+        buttonSpecFile.addTarget(self, action: #selector(showSpecFile), for: .touchUpInside)
     }
     
     @objc func showChooserACKConfidential() {
@@ -359,15 +366,15 @@ class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITex
                     Nexilis.hideLoader { [self] in
                         self.dismiss(animated: true, completion: nil)
                         if (textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines) == "Send message".localized() && textFieldSend.textColor == UIColor.lightGray) {
-                            delegate!.sendChatFromPreviewImage(message_text: "", attachment_flag: "1", image_id: compressedImageName, video_id: "", thumb_id: thumbName, gif_id: "", viewController: self)
+                            delegate!.sendChatFromPreviewImage(message_text: "", attachment_flag: "1", image_id: compressedImageName, video_id: "", thumb_id: thumbName, gif_id: "", viewController: self, specFile: specFileString)
                         } else {
-                            delegate!.sendChatFromPreviewImage(message_text: textFieldSend.text!, attachment_flag: "1", image_id: compressedImageName, video_id: "", thumb_id: thumbName, gif_id: "", viewController: self)
+                            delegate!.sendChatFromPreviewImage(message_text: textFieldSend.text!, attachment_flag: "1", image_id: compressedImageName, video_id: "", thumb_id: thumbName, gif_id: "", viewController: self, specFile: specFileString)
                         }
                     }
                 }
             }
         } else {
-            Nexilis.showLoader()
+            Nexilis.showLoader(text: "Compressing...".localized())
             DispatchQueue.global().async { [self] in
                 var dataVideo: Data?
                 if imageVideoData != nil || urlVideoPhpPicker != nil {
@@ -377,49 +384,32 @@ class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITex
                         dataVideo = try? Data(contentsOf: urlVideoPhpPicker!)
                     }
                 }
-                if let dataVideotoCompress = dataVideo {
-                    let sizeInKB = Double(dataVideotoCompress.count) / 1024.0
-                    let sizeOfVideo = sizeInKB / 1024.0
-                    if (sizeOfVideo > 10.0) {
-                        Nexilis.dispatch = DispatchGroup()
-                        Nexilis.dispatch?.enter()
-                        let compressedURL = NSURL.fileURL(withPath: NSTemporaryDirectory() + UUID().uuidString + ".mp4")
-                        compressVideo(inputURL: (imageVideoData != nil ? imageVideoData![.mediaURL] as? URL : urlVideoPhpPicker)!,
-                                      outputURL: compressedURL) { exportSession in
-                            guard let session = exportSession else {
-                                if let dispatch = Nexilis.dispatch {
-                                    dispatch.leave()
-                                }
+                if dataGIF == nil {
+                    Nexilis.dispatch = DispatchGroup()
+                    Nexilis.dispatch?.enter()
+                    let compressedURL = NSURL.fileURL(withPath: NSTemporaryDirectory() + UUID().uuidString + ".mp4")
+                    compressVideo(inputURL: (imageVideoData != nil ? imageVideoData![.mediaURL] as? URL : urlVideoPhpPicker)!,
+                                  outputURL: compressedURL) { exportSession in
+                        guard let session = exportSession else {
+                            if let dispatch = Nexilis.dispatch {
+                                dispatch.leave()
+                            }
+                            return
+                        }
+                        
+                        if session.status == .completed {
+                            guard let compressedData = try? Data(contentsOf: compressedURL) 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
-                                if let dispatch = Nexilis.dispatch {
-                                    dispatch.leave()
-                                }
-                            case .failed:
-                                break
-                            case .cancelled:
-                                break
-                            @unknown default:
-                                break
+                            dataVideo = compressedData
+                            if let dispatch = Nexilis.dispatch {
+                                dispatch.leave()
                             }
                         }
                     }
+                    Nexilis.dispatch?.wait()
+                    Nexilis.dispatch = nil
                 }
-                Nexilis.dispatch?.wait()
-                Nexilis.dispatch = nil
                 DispatchQueue.main.async {
                     Nexilis.hideLoader { [self] in
                         let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
@@ -438,7 +428,7 @@ class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITex
                                 urlVideo = (urlVideoPhpPicker! as NSURL).absoluteString!
                             }
                             originalVideoName = (urlVideo as NSString).lastPathComponent
-                            renamedVideoName = "Nexilis_video_\(Date().currentTimeMillis())_\(originalVideoName)"
+                            renamedVideoName = "Nexilis_video_\(Date().currentTimeMillis())_\(originalVideoName.components(separatedBy: ".")[0]).mp4"
                             thumbName = "THUMB_Nexilis_video_\(Date().currentTimeMillis())_\(originalVideoName.components(separatedBy: ".")[0]).jpeg"
                         }
                         let fileURL = documentsDirectory.appendingPathComponent(renamedVideoName)
@@ -479,9 +469,9 @@ class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITex
                         }
                         self.dismiss(animated: true, completion: nil)
                         if (textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines) == "Send message".localized() && textFieldSend.textColor == UIColor.lightGray) {
-                            delegate!.sendChatFromPreviewImage(message_text: "", attachment_flag: "2", image_id: "", video_id: renamedVideoName, thumb_id: thumbName, gif_id: dataGIF != nil ? renamedVideoName : "", viewController: self)
+                            delegate!.sendChatFromPreviewImage(message_text: "", attachment_flag: "2", image_id: "", video_id: renamedVideoName, thumb_id: thumbName, gif_id: dataGIF != nil ? renamedVideoName : "", viewController: self, specFile: specFileString)
                         } else {
-                            delegate!.sendChatFromPreviewImage(message_text: textFieldSend.text!, attachment_flag: "2", image_id: "", video_id: renamedVideoName, thumb_id: thumbName, gif_id: dataGIF != nil ? renamedVideoName : "", viewController: self)
+                            delegate!.sendChatFromPreviewImage(message_text: textFieldSend.text!, attachment_flag: "2", image_id: "", video_id: renamedVideoName, thumb_id: thumbName, gif_id: dataGIF != nil ? renamedVideoName : "", viewController: self, specFile: specFileString)
                         }
                     }
                 }
@@ -543,4 +533,135 @@ class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITex
     @objc func cancelTapped() {
         self.dismiss(animated: true, completion: nil)
     }
+    
+    @objc func showSpecFile() {
+        let modalVC = UIViewController()
+        if let viewModal = modalVC.view {
+            viewModal.backgroundColor = .whiteBubbleColor
+            
+            let closeButton = UIButton(type: .close)
+            viewModal.addSubview(closeButton)
+            closeButton.anchor(top: viewModal.topAnchor, right: viewModal.rightAnchor, paddingTop: 15, paddingRight: 15, width: 30, height: 30)
+            closeButton.layer.cornerRadius = 15
+            closeButton.clipsToBounds = true
+            closeButton.backgroundColor = .lightGray.withAlphaComponent(0.1)
+            let config = UIImage.SymbolConfiguration(pointSize: 18, weight: .semibold)
+            closeButton.setImage(UIImage(systemName: "xmark", withConfiguration: config), for: .normal)
+            closeButton.addAction(UIAction { _ in
+                modalVC.dismiss(animated: true)
+            }, for: .touchUpInside)
+            
+            let imageSpec = UIButton(type: .custom)
+            viewModal.addSubview(imageSpec)
+            imageSpec.anchor(top: viewModal.topAnchor, left: viewModal.leftAnchor, paddingTop: 25, paddingLeft: 15, width: 40, height: 40)
+            imageSpec.layer.cornerRadius = 20
+            imageSpec.clipsToBounds = true
+            imageSpec.backgroundColor = .lightGray.withAlphaComponent(0.1)
+            imageSpec.setImage(UIImage(named: "pb_ic_attach_spc", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal).resize(target: CGSize(width: 35, height: 35)), for: .normal)
+            
+            let title = UILabel()
+            title.text = "Option for Attachment".localized()
+            viewModal.addSubview(title)
+            title.anchor(top: viewModal.topAnchor, left: imageSpec.rightAnchor, paddingTop: 23, paddingLeft: 10)
+            title.textColor = .label
+            title.font = .boldSystemFont(ofSize: 16)
+            
+            let subtitle = UILabel()
+            subtitle.text = "Select option :".localized()
+            viewModal.addSubview(subtitle)
+            subtitle.anchor(top: title.bottomAnchor, left: imageSpec.rightAnchor, paddingLeft: 10)
+            subtitle.textColor = .gray
+            subtitle.font = .systemFont(ofSize: 14)
+            
+            tableViewConfigFile = UITableView()
+            viewModal.addSubview(tableViewConfigFile)
+            tableViewConfigFile.backgroundColor = .white
+            tableViewConfigFile.layer.cornerRadius = 8.0
+            tableViewConfigFile.clipsToBounds = true
+            tableViewConfigFile.anchor(top: imageSpec.bottomAnchor, left: viewModal.leftAnchor, bottom: viewModal.bottomAnchor, right: viewModal.rightAnchor, paddingTop: 15, paddingLeft: 15, paddingBottom: 80, paddingRight: 15)
+            tableViewConfigFile.register(UITableViewCell.self, forCellReuseIdentifier: "cellConfigFile")
+            tableViewConfigFile.dataSource = self
+            tableViewConfigFile.delegate = self
+            tableViewConfigFile.separatorStyle = .singleLine
+            tableViewConfigFile.tableFooterView = UIView()
+            if #available(iOS 15.0, *) {
+                tableViewConfigFile.sectionHeaderTopPadding = 0
+            }
+            
+            if #available(iOS 15.0, *) {
+                if let sheet = modalVC.sheetPresentationController {
+                    sheet.detents = [.medium()]
+                }
+            } else {
+                // Fallback on earlier versions
+            }
+        }
+        UIApplication.shared.visibleViewController?.present(modalVC, animated: true)
+    }
+}
+
+extension PreviewAttachmentImageVideo: UITableViewDelegate, UITableViewDataSource {
+    func numberOfSections(in tableView: UITableView) -> Int {
+        return 1
+    }
+    
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return 2
+    }
+    
+    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
+        return nil
+    }
+    
+    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
+        return 0
+    }
+    
+    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        tableView.deselectRow(at: indexPath, animated: true)
+        var type = ""
+        if indexPath.row == 0 {
+            type = "share,download"
+        } else {
+            type = "forward"
+        }
+        if !specFileString.contains(type) {
+            if !specFileString.isEmpty {
+                specFileString += ","
+            }
+            specFileString += type
+        } else {
+            specFileString = specFileString.replacingOccurrences(of: type, with: "")
+            if specFileString == "," {
+                specFileString = ""
+            }
+        }
+        if specFileString.isEmpty {
+            buttonSpecFile.setImage(UIImage(named: "pb_ic_attach_spc_off", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal).resize(target: CGSize(width: 40, height: 40)), for: .normal)
+        } else {
+            buttonSpecFile.setImage(UIImage(named: "pb_ic_attach_spc", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal).resize(target: CGSize(width: 40, height: 40)), for: .normal)
+        }
+        tableView.reloadData()
+    }
+    
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let cell = tableView.dequeueReusableCell(withIdentifier: "cellConfigFile", for: indexPath as IndexPath)
+        var content = cell.defaultContentConfiguration()
+        content.textProperties.font = .systemFont(ofSize: 16, weight: .medium)
+        content.textProperties.color = .label
+        content.secondaryTextProperties.font = .systemFont(ofSize: 14)
+        content.secondaryTextProperties.color = .gray
+        if indexPath.row == 0 {
+            content.text = "Can Share and Download".localized()
+            content.secondaryText = "The user, as the receiver, can share and download the attachment.".localized()
+            cell.accessoryType = specFileString.contains("share,download") ? .checkmark : .none
+        } else {
+            content.text = "Can Forward".localized()
+            content.secondaryText = "The user, as the receiver, can forward the attachment.".localized()
+            cell.accessoryType = specFileString.contains("forward") ? .checkmark : .none
+        }
+        cell.contentConfiguration = content
+        cell.tintColor = .black
+        return cell
+    }
 }

+ 0 - 124
NexilisLite/NexilisLite/Source/View/Chat/PreviewAttachmentImageVideo.xib

@@ -1,124 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
-    <device id="retina6_1" orientation="portrait" appearance="light"/>
-    <dependencies>
-        <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22684"/>
-        <capability name="Image references" minToolsVersion="12.0"/>
-        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
-        <capability name="System colors in document resources" minToolsVersion="11.0"/>
-        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
-    </dependencies>
-    <objects>
-        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="PreviewAttachmentImageVideo" customModule="NexilisLite" customModuleProvider="target">
-            <connections>
-                <outlet property="buttonAckConfidential" destination="m6t-Me-xFW" id="36g-1Y-Jgr"/>
-                <outlet property="buttonCancel" destination="kDJ-TL-vNL" id="Xo6-YX-9ma"/>
-                <outlet property="buttonSend" destination="fNr-UI-Smq" id="m00-6U-HAF"/>
-                <outlet property="constraintButtonAckCondential" destination="GWK-9A-opG" id="s7K-bT-3rj"/>
-                <outlet property="constraintButtonSend" destination="CSE-Zv-Ou4" id="6bU-Mw-auh"/>
-                <outlet property="constraintLeftTextField" destination="KXD-ok-nGH" id="dqn-N5-WLn"/>
-                <outlet property="constraintViewTextField" destination="nbp-vo-svJ" id="WnY-VM-DhU"/>
-                <outlet property="heightTextFieldSend" destination="4X4-Hj-TzA" id="BLV-O8-avl"/>
-                <outlet property="imagePreview" destination="1kP-2x-Lvs" id="CAj-mk-x5G"/>
-                <outlet property="scrollViewImage" destination="fUG-ls-mSU" id="u0l-KJ-SaX"/>
-                <outlet property="textFieldSend" destination="TU8-ei-nsO" id="Sbn-aC-X2b"/>
-                <outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
-            </connections>
-        </placeholder>
-        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
-        <view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
-            <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
-            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
-            <subviews>
-                <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" bounces="NO" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fUG-ls-mSU">
-                    <rect key="frame" x="0.0" y="48" width="414" height="814"/>
-                    <subviews>
-                        <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="1kP-2x-Lvs">
-                            <rect key="frame" x="0.0" y="0.0" width="414" height="814"/>
-                            <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                        </imageView>
-                    </subviews>
-                    <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                    <constraints>
-                        <constraint firstItem="1kP-2x-Lvs" firstAttribute="top" secondItem="fUG-ls-mSU" secondAttribute="top" id="BRw-Qf-Iem"/>
-                        <constraint firstAttribute="trailing" secondItem="1kP-2x-Lvs" secondAttribute="trailing" id="Fbf-E3-xAu"/>
-                        <constraint firstItem="1kP-2x-Lvs" firstAttribute="leading" secondItem="fUG-ls-mSU" secondAttribute="leading" id="IhT-0L-tSH"/>
-                        <constraint firstAttribute="bottom" secondItem="1kP-2x-Lvs" secondAttribute="bottom" id="LnA-sx-iQR"/>
-                        <constraint firstItem="1kP-2x-Lvs" firstAttribute="height" secondItem="fUG-ls-mSU" secondAttribute="height" id="Vg8-Eq-NiL"/>
-                        <constraint firstItem="1kP-2x-Lvs" firstAttribute="width" secondItem="fUG-ls-mSU" secondAttribute="width" id="bXj-2J-Lqd"/>
-                    </constraints>
-                </scrollView>
-                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="kDJ-TL-vNL" userLabel="Button Cancel">
-                    <rect key="frame" x="10" y="58" width="40" height="40"/>
-                    <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
-                    <constraints>
-                        <constraint firstAttribute="width" constant="40" id="axp-R5-jBp"/>
-                        <constraint firstAttribute="height" constant="40" id="xsU-84-Xow"/>
-                    </constraints>
-                    <color key="tintColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                    <state key="normal">
-                        <imageReference key="image" image="xmark" catalog="system" symbolScale="large"/>
-                    </state>
-                </button>
-                <textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" showsHorizontalScrollIndicator="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="TU8-ei-nsO" customClass="CustomTextView" customModule="NexilisLite" customModuleProvider="target">
-                    <rect key="frame" x="65" y="802" width="329" height="40"/>
-                    <constraints>
-                        <constraint firstAttribute="height" constant="40" id="4X4-Hj-TzA"/>
-                    </constraints>
-                    <color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                    <fontDescription key="fontDescription" type="system" pointSize="14"/>
-                    <textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
-                </textView>
-                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="m6t-Me-xFW">
-                    <rect key="frame" x="20" y="802" width="40" height="40"/>
-                    <color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                    <constraints>
-                        <constraint firstAttribute="width" constant="40" id="osz-OV-n7L"/>
-                        <constraint firstAttribute="height" constant="40" id="rF0-lI-vgS"/>
-                    </constraints>
-                    <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                    <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
-                    <state key="normal">
-                        <imageReference key="image" image="gearshape.fill" catalog="system" symbolScale="large"/>
-                    </state>
-                </button>
-                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="fNr-UI-Smq">
-                    <rect key="frame" x="354" y="802" width="40" height="40"/>
-                    <color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                    <constraints>
-                        <constraint firstAttribute="width" constant="40" id="T6T-5w-Lbw"/>
-                        <constraint firstAttribute="height" constant="40" id="pOx-hl-HnF"/>
-                    </constraints>
-                    <state key="normal" image="Send-(White)"/>
-                </button>
-            </subviews>
-            <viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
-            <color key="backgroundColor" systemColor="systemBackgroundColor"/>
-            <constraints>
-                <constraint firstItem="fnl-2z-Ty3" firstAttribute="trailing" secondItem="TU8-ei-nsO" secondAttribute="trailing" constant="20" id="3y7-yB-fKn"/>
-                <constraint firstItem="fUG-ls-mSU" firstAttribute="trailing" secondItem="fnl-2z-Ty3" secondAttribute="trailing" id="52V-w7-M51"/>
-                <constraint firstItem="fnl-2z-Ty3" firstAttribute="bottom" secondItem="fNr-UI-Smq" secondAttribute="bottom" constant="20" id="CSE-Zv-Ou4"/>
-                <constraint firstItem="fUG-ls-mSU" firstAttribute="top" secondItem="fnl-2z-Ty3" secondAttribute="top" id="CYI-Qr-Veh"/>
-                <constraint firstItem="fUG-ls-mSU" firstAttribute="bottom" secondItem="fnl-2z-Ty3" secondAttribute="bottom" id="DGd-5J-aUQ"/>
-                <constraint firstItem="fnl-2z-Ty3" firstAttribute="bottom" secondItem="m6t-Me-xFW" secondAttribute="bottom" constant="20" id="GWK-9A-opG"/>
-                <constraint firstItem="TU8-ei-nsO" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" constant="65" id="KXD-ok-nGH"/>
-                <constraint firstItem="fUG-ls-mSU" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" id="Omw-Qi-oJp"/>
-                <constraint firstItem="m6t-Me-xFW" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" constant="20" id="QKt-Mv-7uC"/>
-                <constraint firstItem="kDJ-TL-vNL" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" constant="10" id="dWu-IR-Bty"/>
-                <constraint firstItem="fnl-2z-Ty3" firstAttribute="bottom" secondItem="TU8-ei-nsO" secondAttribute="bottom" constant="20" id="nbp-vo-svJ"/>
-                <constraint firstItem="kDJ-TL-vNL" firstAttribute="top" secondItem="fnl-2z-Ty3" secondAttribute="top" constant="10" id="wQl-qg-xOE"/>
-                <constraint firstItem="fnl-2z-Ty3" firstAttribute="trailing" secondItem="fNr-UI-Smq" secondAttribute="trailing" constant="20" id="ys4-il-caW"/>
-            </constraints>
-            <point key="canvasLocation" x="-62.318840579710148" y="99.776785714285708"/>
-        </view>
-    </objects>
-    <resources>
-        <image name="Send-(White)" width="500" height="500"/>
-        <image name="gearshape.fill" catalog="system" width="128" height="123"/>
-        <image name="xmark" catalog="system" width="128" height="113"/>
-        <systemColor name="systemBackgroundColor">
-            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-        </systemColor>
-    </resources>
-</document>