Bläddra i källkod

secure folder

kevin 8 månader sedan
förälder
incheckning
88396da9d1

+ 4 - 0
NexilisLite/NexilisLite.xcodeproj/project.pbxproj

@@ -9,6 +9,7 @@
 /* Begin PBXBuildFile section */
 		1241AEBC2D017E8C0088175A /* MasterKeyUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1241AEBB2D017E8C0088175A /* MasterKeyUtil.swift */; };
 		1241AEBD2D017E8C0088175A /* FileEncryption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1241AEBA2D017E8C0088175A /* FileEncryption.swift */; };
+		12C36CEB2D0299630095BEC1 /* SecureFolderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12C36CEA2D02995F0095BEC1 /* SecureFolderView.swift */; };
 		3AA890FECDE6363EFE1216A2 /* Pods_NexilisLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05DE800165B7FA8135DD726B /* Pods_NexilisLite.framework */; };
 		6727D8C17B895F1A290AA89B /* Pods_NexilisLite_NexilisLiteTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F397F48A7642BC0DB5A8D144 /* Pods_NexilisLite_NexilisLiteTests.framework */; };
 		CD1E6E6D2A0B7C3600BF871F /* NexilisLite.docc in Sources */ = {isa = PBXBuildFile; fileRef = CD1E6E6C2A0B7C3600BF871F /* NexilisLite.docc */; };
@@ -256,6 +257,7 @@
 		05DE800165B7FA8135DD726B /* Pods_NexilisLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NexilisLite.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		1241AEBA2D017E8C0088175A /* FileEncryption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileEncryption.swift; sourceTree = "<group>"; };
 		1241AEBB2D017E8C0088175A /* MasterKeyUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterKeyUtil.swift; sourceTree = "<group>"; };
+		12C36CEA2D02995F0095BEC1 /* SecureFolderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureFolderView.swift; sourceTree = "<group>"; };
 		2B2ED86C8610C9011EBC7B85 /* Pods-NexilisLite-NexilisLiteTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NexilisLite-NexilisLiteTests.release.xcconfig"; path = "Target Support Files/Pods-NexilisLite-NexilisLiteTests/Pods-NexilisLite-NexilisLiteTests.release.xcconfig"; sourceTree = "<group>"; };
 		36C68FFC41F4DA0F806DD5BD /* Pods-NexilisLite.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NexilisLite.debug.xcconfig"; path = "Target Support Files/Pods-NexilisLite/Pods-NexilisLite.debug.xcconfig"; sourceTree = "<group>"; };
 		506228B53B8F36AD707C0E89 /* Pods-NexilisLite.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NexilisLite.release.xcconfig"; path = "Target Support Files/Pods-NexilisLite/Pods-NexilisLite.release.xcconfig"; sourceTree = "<group>"; };
@@ -669,6 +671,7 @@
 		CD1E716D2A0BA86100BF871F /* Chat */ = {
 			isa = PBXGroup;
 			children = (
+				12C36CEA2D02995F0095BEC1 /* SecureFolderView.swift */,
 				CD7054E92AD39DE2003741BF /* ChatGPTBotView.swift */,
 				CD1E71742A0BA86100BF871F /* CustomTextView.swift */,
 				CD1E71722A0BA86100BF871F /* EditorGroup.swift */,
@@ -1259,6 +1262,7 @@
 				CD1E721A2A0BA86100BF871F /* NotifSound.swift in Sources */,
 				CD1E724F2A0BA86100BF871F /* SetInternalCSAccount.swift in Sources */,
 				CDA461222AB99E09001CD010 /* ConfigureFloatingButton.swift in Sources */,
+				12C36CEB2D0299630095BEC1 /* SecureFolderView.swift in Sources */,
 				CD1E721C2A0BA86100BF871F /* ContactCallViewController.swift in Sources */,
 				CD5A73AE2A77642D000541A5 /* MessageInfo.swift in Sources */,
 				CD1E723B2A0BA86100BF871F /* GroupDescViewController.swift in Sources */,

+ 27 - 12
NexilisLite/NexilisLite/Resource/Palio.storyboard

@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23094" 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="23506"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23084"/>
         <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"/>
@@ -603,6 +603,21 @@
             </objects>
             <point key="canvasLocation" x="8525" y="856"/>
         </scene>
+        <!--Secure Folder View Controller-->
+        <scene sceneID="0CW-ui-YaM">
+            <objects>
+                <viewController storyboardIdentifier="secureFolderView" useStoryboardIdentifierAsRestorationIdentifier="YES" id="SWe-Or-R5d" customClass="SecureFolderViewController" customModule="NexilisLite" customModuleProvider="target" sceneMemberID="viewController">
+                    <view key="view" contentMode="scaleToFill" id="oPU-EK-DRC">
+                        <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <viewLayoutGuide key="safeArea" id="EkW-9M-MxF"/>
+                        <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="tff-fc-cyw" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="9367" y="861"/>
+        </scene>
         <!--Change Name Table View Controller-->
         <scene sceneID="cBS-F8-cFW">
             <objects>
@@ -3275,37 +3290,37 @@
             <color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
         </systemColor>
         <systemColor name="secondarySystemBackgroundColor">
-            <color red="0.94901960784313721" green="0.94901960784313721" blue="0.96862745098039216" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+            <color red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
         </systemColor>
         <systemColor name="systemBackgroundColor">
             <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
         </systemColor>
         <systemColor name="systemBlueColor">
-            <color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+            <color red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
         </systemColor>
         <systemColor name="systemGray2Color">
-            <color red="0.68235294117647061" green="0.68235294117647061" blue="0.69803921568627447" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+            <color red="0.68235294120000001" green="0.68235294120000001" blue="0.69803921570000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
         </systemColor>
         <systemColor name="systemGray3Color">
-            <color red="0.7803921568627451" green="0.7803921568627451" blue="0.80000000000000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+            <color red="0.78039215689999997" green="0.78039215689999997" blue="0.80000000000000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
         </systemColor>
         <systemColor name="systemGray4Color">
-            <color red="0.81960784313725488" green="0.81960784313725488" blue="0.83921568627450982" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+            <color red="0.81960784310000001" green="0.81960784310000001" blue="0.83921568629999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
         </systemColor>
         <systemColor name="systemGrayColor">
-            <color red="0.55686274509803924" green="0.55686274509803924" blue="0.57647058823529407" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+            <color red="0.5568627451" green="0.5568627451" blue="0.57647058819999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
         </systemColor>
         <systemColor name="systemGreenColor">
-            <color red="0.20392156862745098" green="0.7803921568627451" blue="0.34901960784313724" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+            <color red="0.20392156859999999" green="0.78039215689999997" blue="0.34901960780000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
         </systemColor>
         <systemColor name="systemIndigoColor">
-            <color red="0.34509803921568627" green="0.33725490196078434" blue="0.83921568627450982" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+            <color red="0.34509803919999998" green="0.33725490200000002" blue="0.83921568629999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
         </systemColor>
         <systemColor name="systemRedColor">
-            <color red="1" green="0.23137254901960785" blue="0.18823529411764706" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+            <color red="1" green="0.23137254900000001" blue="0.18823529410000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
         </systemColor>
         <systemColor name="tintColor">
-            <color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+            <color red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
         </systemColor>
     </resources>
 </document>

+ 309 - 0
NexilisLite/NexilisLite/Source/View/Chat/SecureFolderView.swift

@@ -0,0 +1,309 @@
+//
+//  SecureFolderView.swift
+//  NexilisLite
+//
+//  Created by Maronakins on 06/12/24.
+//
+
+import UIKit
+import AVFoundation
+import AVKit
+import QuickLook
+
+class SecureFolderViewController: UIViewController, UISearchBarDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, QLPreviewControllerDataSource {
+    
+    var directoryPath: URL!
+    var files: [SecureFolderItem] = []
+    var filteredFiles: [SecureFolderItem] = []
+    var previewItem: NSURL?
+    
+    let searchBar: UISearchBar = {
+        let sb = UISearchBar()
+        sb.placeholder = "Search files"
+        return sb
+    }()
+    
+    let collectionView: UICollectionView = {
+        let layout = UICollectionViewFlowLayout()
+        layout.minimumLineSpacing = 10
+        layout.minimumInteritemSpacing = 10
+        
+        let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
+        cv.backgroundColor = .white
+        cv.register(FileCell.self, forCellWithReuseIdentifier: "FileCell")
+        return cv
+    }()
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        self.title = "Secure Folder"
+        view.backgroundColor = .white
+        
+        setupSubviews()
+        loadFiles()
+        filteredFiles = files
+    }
+    
+    func getThumbnail(for fileName: String) -> UIImage? {
+        // Logic to get the file thumbnail if it exists.
+        // This could include generating a thumbnail from the actual file.
+        // Placeholder image is used here as an example.
+        let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
+        let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
+        let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
+        if let dirPath = paths.first {
+            let thumbURL = URL(fileURLWithPath: dirPath).appendingPathComponent(fileName)
+            return UIImage(contentsOfFile: thumbURL.path) ?? UIImage(systemName: "photo")?.withTintColor(.black)
+        }
+        return UIImage(systemName: "text.document.fill")?.withTintColor(.black) // Replace with actual thumbnail logic
+    }
+
+    func setupSubviews() {
+        // Add search bar
+        searchBar.delegate = self
+        view.addSubview(searchBar)
+        searchBar.translatesAutoresizingMaskIntoConstraints = false
+        NSLayoutConstraint.activate([
+            searchBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
+            searchBar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+            searchBar.trailingAnchor.constraint(equalTo: view.trailingAnchor)
+        ])
+        
+        // Add collection view
+        collectionView.dataSource = self
+        collectionView.delegate = self
+        view.addSubview(collectionView)
+        collectionView.translatesAutoresizingMaskIntoConstraints = false
+        NSLayoutConstraint.activate([
+            collectionView.topAnchor.constraint(equalTo: searchBar.bottomAnchor),
+            collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+            collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+            collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
+        ])
+    }
+    
+    func loadFiles() {
+        do {
+            var query = "SELECT audio_id, video_id, image_id, thumb_id, file_id, attachment_flag, status, server_date FROM MESSAGE where (video_id IS NOT NULL AND video_id != '') OR (image_id IS NOT NULL AND image_id != '') OR (file_id IS NOT NULL AND file_id != '') order by server_date asc"
+            Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                do {
+                    if let cursorData = Database.shared.getRecords(fmdb: fmdb, query: query) {
+                        while cursorData.next() {
+                            var fileName = ""
+                            let audioId = cursorData.string(forColumn: "audio_id") ?? ""
+                            let videoId = cursorData.string(forColumn: "video_id") ?? ""
+                            let imageId = cursorData.string(forColumn: "image_id") ?? ""
+                            let thumbId = cursorData.string(forColumn: "thumb_id") ?? ""
+                            let fileId = cursorData.string(forColumn: "file_id") ?? ""
+                            let attachmentFlag = cursorData.string(forColumn: "attachment_flag") ?? ""
+                            let status = cursorData.string(forColumn: "status") ?? ""
+                            let serverDate = cursorData.string(forColumn: "server_date") ?? ""
+                            if imageId != "" {
+                                fileName = imageId
+                            }
+                            else if videoId != "" {
+                                fileName = videoId
+                            }
+                            else if fileId != "" {
+                                fileName = fileId
+                            }
+                            else if audioId != "" {
+                                fileName = audioId
+                            }
+                            if FileEncryption.shared.isSecureExists(filename: fileName) {
+                                let secureFolderItem = SecureFolderItem(audioId: audioId, videoId: videoId, imageId: imageId, fileId: fileId, thumbId: thumbId, attachmentFlag: attachmentFlag, serverDate: serverDate, status: status, filename: fileName)
+                                files.append(secureFolderItem)
+                            }
+                        }
+                        cursorData.close()
+                    }
+                }
+                catch {
+                    rollback.pointee = true
+                    print("Access database error: \(error.localizedDescription)")
+                }
+            })
+        } catch {
+            print("Error loading files: \(error.localizedDescription)")
+        }
+    }
+
+    // MARK: Search Bar Delegate
+    
+    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
+        filteredFiles = searchText.isEmpty ? files : files.filter { $0.filename.contains(searchText) }
+        collectionView.reloadData()
+    }
+    
+    // MARK: Collection View Data Source
+    
+    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+        return filteredFiles.count
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FileCell", for: indexPath) as! FileCell
+        let fileItem = filteredFiles[indexPath.item]
+        cell.label.text = fileItem.filename
+        cell.imageView.tintColor = .black
+        cell.imageView.sizeThatFits(CGSize(width: 200.0, height: 200.0))
+        var thumbnailImage = UIImage(systemName: "doc.text")?.withTintColor(.black)
+        if fileItem.thumbId != "" {
+            thumbnailImage = getThumbnail(for: fileItem.thumbId)
+        } else if fileItem.audioId != "" {
+            thumbnailImage = UIImage(systemName: "speaker.wave.3")?.withTintColor(.black)
+        }
+        cell.imageView.image = thumbnailImage
+
+        return cell
+    }
+    
+    // MARK: Collection View Delegate Flow Layout
+    
+    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
+        let padding: CGFloat = 10
+        let collectionViewSize = collectionView.frame.size.width - padding
+        let width = collectionViewSize / 2
+        return CGSize(width: width, height: width)
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+        let fileItem = filteredFiles[indexPath.item]
+        if fileItem.imageId != "" {
+            print("this image")
+            do {
+                let data = try FileEncryption.shared.readSecure(filename: fileItem.filename)
+                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)
+            }
+            catch {
+                print("Error reading secure file")
+            }
+        }
+        else if fileItem.videoId != "" {
+            print("this video")
+            do {
+                if let secureData = try FileEncryption.shared.readSecure(filename: fileItem.filename) {
+                    let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
+                    let tempPath = cachesDirectory.appendingPathComponent(fileItem.filename)
+                    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)
+                }
+            } catch {
+                
+            }
+        }
+        else if fileItem.fileId != "" {
+            print("this file")
+            do {
+                if let docData = try FileEncryption.shared.readSecure(filename: fileItem.filename) {
+                    
+                    let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
+                    let tempPath = cachesDirectory.appendingPathComponent(fileItem.filename)
+                    try docData.write(to: tempPath)
+                    self.previewItem = tempPath as NSURL
+                    let previewController = QLPreviewController()
+                    let rightBarButton = UIBarButtonItem()
+                    previewController.navigationItem.rightBarButtonItem = rightBarButton
+                    previewController.dataSource = self
+                    previewController.modalPresentationStyle = .custom
+                    self.present(previewController,animated: true)
+                }
+            }
+            catch {
+                
+            }
+        }
+    }
+    
+    public func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
+        return self.previewItem != nil ? 1 : 0
+    }
+    
+    public func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
+        return self.previewItem!
+    }
+    
+}
+                                                    
+struct SecureFolderItem {
+    let audioId: String
+    let videoId: String
+    let imageId: String
+    let fileId: String
+    let thumbId: String
+    let attachmentFlag: String
+    let serverDate: String
+    let status: String
+    let filename: String
+    
+    init(audioId: String, videoId: String, imageId: String, fileId: String, thumbId: String, attachmentFlag: String, serverDate: String, status: String, filename: String) {
+        self.audioId = audioId
+        self.videoId = videoId
+        self.imageId = imageId
+        self.fileId = fileId
+        self.thumbId = thumbId
+        self.attachmentFlag = attachmentFlag
+        self.serverDate = serverDate
+        self.status = status
+        self.filename = filename
+    }
+}
+
+// Custom UICollectionViewCell
+class FileCell: UICollectionViewCell {
+    
+    let imageView: UIImageView = {
+        let iv = UIImageView()
+        iv.contentMode = .scaleAspectFit
+        iv.clipsToBounds = true
+        iv.image = UIImage(systemName: "doc") // Default placeholder image
+        return iv
+    }()
+    
+    let label: UILabel = {
+        let lbl = UILabel()
+        lbl.textAlignment = .center
+        lbl.numberOfLines = 1
+        lbl.font = UIFont.systemFont(ofSize: 12)
+        return lbl
+    }()
+    
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+                
+        contentView.addSubview(imageView)
+        contentView.addSubview(label)
+        
+        imageView.translatesAutoresizingMaskIntoConstraints = false
+        label.translatesAutoresizingMaskIntoConstraints = false
+
+        NSLayoutConstraint.activate([
+            imageView.topAnchor.constraint(equalTo: contentView.topAnchor),
+            imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
+            imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
+            imageView.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: 0.75),
+
+            label.topAnchor.constraint(equalTo: imageView.bottomAnchor),
+            label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
+            label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
+            label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
+        ])
+        
+        layer.borderWidth = 1.0
+        layer.borderColor = UIColor.black.cgColor
+    }
+    
+    required init?(coder: NSCoder) {
+        super.init(coder: coder)
+    }
+}

+ 11 - 0
NexilisLite/NexilisLite/Source/View/Control/SettingTableViewController.swift

@@ -117,6 +117,7 @@ public class SettingTableViewController: UITableViewController, UIGestureRecogni
                             Item.menus["Personal"] = [
                                 Item(icon: UIImage(systemName: "person"), title: "Personal Information".localized()),
                                 Item(icon: UIImage(systemName: "textformat.abc"), title: "Change Language".localized()),
+                                Item(icon: UIImage(systemName: "lock"), title: "Secure Folder"),
                                 Item(icon: UIImage(systemName: "person.crop.rectangle"), title: "Change Admin / Internal Password".localized()),
                                 Item(icon: UIImage(systemName: "laptopcomputer.and.iphone"), title: "Sign-In to Web".localized()),
                                 Item(icon: UIImage(named: "ic_internal", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, title: "Set Internal Account".localized()),
@@ -126,12 +127,14 @@ public class SettingTableViewController: UITableViewController, UIGestureRecogni
                             Item.menus["Personal"] = [
                                 Item(icon: UIImage(systemName: "person"), title: "Personal Information".localized()),
                                 Item(icon: UIImage(systemName: "textformat.abc"), title: "Change Language".localized()),
+                                Item(icon: UIImage(systemName: "lock"), title: "Secure Folder"),
                                 Item(icon: UIImage(systemName: "laptopcomputer.and.iphone"), title: "Sign-In to Web".localized()),
                             ]
                         } else {
                             Item.menus["Personal"] = [
                                 Item(icon: UIImage(systemName: "person"), title: "Personal Information".localized()),
                                     Item(icon: UIImage(systemName: "textformat.abc"), title: "Change Language".localized()),
+                                Item(icon: UIImage(systemName: "lock"), title: "Secure Folder"),
                                 Item(icon: UIImage(systemName: "person.badge.key"), title: "Access Admin / Internal Features".localized()),
                             ]
                         }
@@ -185,6 +188,7 @@ public class SettingTableViewController: UITableViewController, UIGestureRecogni
                         Item.menus["Personal"] = [
                             Item(icon: UIImage(systemName: "person"), title: "Personal Information".localized()),
                                 Item(icon: UIImage(systemName: "textformat.abc"), title: "Change Language".localized()),
+                            Item(icon: UIImage(systemName: "lock"), title: "Secure Folder"),
                             Item(icon: UIImage(systemName: "person.badge.key"), title: "Access Admin / Internal Features".localized()),
                         ]
                         if Nexilis.showButtonFB {
@@ -323,6 +327,8 @@ public class SettingTableViewController: UITableViewController, UIGestureRecogni
             switch menu.title {
             case "Personal Information".localized():
                 cell.accessoryType = .disclosureIndicator
+            case "Secure Folder".localized():
+                cell.accessoryType = .disclosureIndicator
             case "Access Admin / Internal Features".localized():
                 cell.accessoryType = .disclosureIndicator
             case "Sign-In to Web".localized():
@@ -435,6 +441,11 @@ public class SettingTableViewController: UITableViewController, UIGestureRecogni
                 }
                 navigationController?.show(controller, sender: nil)
             }
+        } else if item.title == "Secure Folder" {
+            if(Nexilis.checkIsChangePerson()){
+                let controller = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "secureFolderView") as! SecureFolderViewController
+                navigationController?.show(controller, sender: nil)
+            }
         } else if item.title == "Access Admin / Internal Features".localized() || item.title == "Change Admin / Internal Password".localized() {
             if(Nexilis.checkIsChangePerson()){
                 if !CheckConnection.isConnectedToNetwork()  || API.nGetCLXConnState() == 0 {