Ver código fonte

update and fix bugs for 5.0.38

alqindiirsyam 3 meses atrás
pai
commit
297509e761

+ 1 - 1
AppBuilder/AppBuilder/FirstTabViewController.swift

@@ -105,7 +105,7 @@ class FirstTabViewController: UIViewController, UIScrollViewDelegate, UIGestureR
         
         NotificationCenter.default.addObserver(self, selector: #selector(onShowAC(notification:)), name: NSNotification.Name(rawValue: "onShowAC"), object: nil)
         NotificationCenter.default.addObserver(self, selector: #selector(onRefreshWebView(notification:)), name: NSNotification.Name(rawValue: "onRefreshWebView"), object: nil)
-        NotificationCenter.default.addObserver(self, selector: #selector(onRefreshWebView(notification:)), name: UIApplication.willEnterForegroundNotification, object: nil)
+//        NotificationCenter.default.addObserver(self, selector: #selector(onRefreshWebView(notification:)), name: UIApplication.willEnterForegroundNotification, object: nil)
         FirstTabViewController.canLoadURL = true
         processURL()
     }

+ 9 - 15
AppBuilder/AppBuilder/SecondTabViewController.swift

@@ -36,7 +36,7 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
     
     var noData = false
     var loadingData = true
-    var waitingLoading = false
+    var timerReloadData = Timer()
     
     let textViewSearch = UITextField()
     let buttonImageVoiceSb = UIButton(type: .custom)
@@ -767,17 +767,11 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
     }
     
     private func reloadAllData() {
-        DispatchQueue.global().async { [self] in
-            if waitingLoading {
-                return
-            }
-            waitingLoading = true
-            while loadingData {
-                Thread.sleep(forTimeInterval: 0.5)
-            }
-            waitingLoading = false
-            getData()
-        }
+        timerReloadData.invalidate()
+        timerReloadData = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: {_ in
+            self.getData()
+        })
+        timerReloadData.fire()
     }
     
     @objc func onReloadTab(notification: NSNotification) {
@@ -1755,7 +1749,7 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
                     if let dirPath = paths.first {
                         let audioURL = URL(fileURLWithPath: dirPath).appendingPathComponent(data.audio)
                         if !FileManager.default.fileExists(atPath: audioURL.path) && !FileEncryption.shared.isSecureExists(filename: data.audio) {
-                            Download().startHTTP(forKey: data.audio, isImage: false) { (name, progress) in
+                            Download().startHTTP(forKey: data.audio) { (name, progress) in
                                 guard progress == 100 else {
                                     return
                                 }
@@ -2266,7 +2260,7 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
             if selectedTag == GIFS_TAG {
                 let gifURL = URL(fileURLWithPath: dirPath).appendingPathComponent(gifData)
                 if !FileManager.default.fileExists(atPath: gifURL.path) && !FileEncryption.shared.isSecureExists(filename: gifData) {
-                    Download().startHTTP(forKey: gifData, isImage: false) { (name, progress) in
+                    Download().startHTTP(forKey: gifData) { (name, progress) in
                         guard progress == 100 else {
                             return
                         }
@@ -2410,7 +2404,7 @@ extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource {
                         
                     }
                 } else {
-                    Download().startHTTP(forKey: vidData, isImage: false) { (name, progress) in
+                    Download().startHTTP(forKey: vidData) { (name, progress) in
                         guard progress == 100 else {
                             return
                         }

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

@@ -1433,7 +1433,7 @@ public class APIS: NSObject {
                                 navigationC.popViewController(animated: false)
                             }
                         }
-                        showEditorOrCallFromAPN(pin, message_scope_id == "4" ? "1" : "0", "CL01")
+                        showEditorOrCallFromAPN(pin, message_scope_id == "4" ? "1" : !message_scope_id.isEmpty ? "0" : "", "CL01")
                     }
                 }
             }
@@ -1442,6 +1442,9 @@ public class APIS: NSObject {
     }
     
     private static func showEditorOrCallFromAPN(_ id: String, _ type: String, _ callType: String) {
+        if type.isEmpty {
+            return
+        }
         if type == "0" {
             if User.getDataCanNil(pin: id) == nil && id != "-999" && id != "-997" {
                 return
@@ -1588,6 +1591,7 @@ public class APIS: NSObject {
 //            API.deinitConnection()
 //        }
         notifTimer.invalidate()
+        stopNotif = true
     }
     
     public static var notifTimer = Timer()

+ 8 - 12
NexilisLite/NexilisLite/Source/Download.swift

@@ -46,14 +46,14 @@ public class Download {
     }
     
     public func startHTTP(forKey: String, downloadUrl: String, completion: @escaping (String, Double)->()) {
-        _ = startHTTP(filename: forKey, isImage: false, baseURL: downloadUrl, completion: completion)
+        startHTTP(filename: forKey, baseURL: downloadUrl, completion: completion)
     }
     
-    public func startHTTP(forKey: String, isImage: Bool = false, completion: @escaping (String, Double)->()) {
-        _ = startHTTP(filename: forKey, isImage: isImage, baseURL: DOWNLOAD_URL, completion: completion)
+    public func startHTTP(forKey: String, completion: @escaping (String, Double)->()) {
+        startHTTP(filename: forKey, baseURL: DOWNLOAD_URL, completion: completion)
     }
     
-    public func startHTTP(filename: String, isImage: Bool = true, baseURL: String, completion: @escaping (String, Double)->()) {
+    public func startHTTP(filename: String, baseURL: String, completion: @escaping (String, Double)->()) {
         let download = Nexilis.getDownload(forKey: filename)
         if download == nil {
             Nexilis.addDownload(forKey: filename, download: self)
@@ -76,9 +76,9 @@ public class Download {
                 "User-Agent": Utils.getUserAgent(),
                 "Cookie": Utils.getCookiesMobile()
             ]
-            //print("HEADER: \(headers)")
+            print("FULL URL: \(fullURL)")
             do {
-                let downloadRequest = SessionManager.shared.session.download(fullURL, headers: headers)
+                _ = SessionManager.shared.session.download(fullURL, headers: headers)
                 .downloadProgress(queue: downloadBufferQueue) { progress in
                     let frac = progress.fractionCompleted*100
                     if frac != 100.0 {
@@ -87,12 +87,8 @@ public class Download {
                 }
                 .responseData { result in
                     if let response = result.response, response.statusCode == 200, let successResponse = result.value {
-                        //print("Response success")
+                        print("Response success")
                         do {
-                            let documentDir = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
-                            let url = documentDir.appendingPathComponent(filename)
-//                            print("write file \(url.path)")
-//                            try successResponse.write(to: url)
                             let dResponse = FileEncryption.shared.decryptFileFromServer(data: successResponse)
                             if dResponse != nil {
                                 try FileEncryption.shared.writeSecure(filename: filename, data: dResponse!)
@@ -100,9 +96,9 @@ public class Download {
                                 try FileEncryption.shared.writeSecure(filename: filename, data: successResponse)
                             }
                             _ = Nexilis.removeDownload(forKey: filename)
-                            completion(filename,100)
                         }
                         catch {}
+                        completion(filename,100)
                     }
                     else {
 //                        let statusCode = result.response?.statusCode

+ 27 - 17
NexilisLite/NexilisLite/Source/Extension.swift

@@ -862,17 +862,14 @@ extension String {
         for match in matches.reversed() {
             let range = match.range(at: 1)
             let username = (text.string as NSString).substring(with: range)
-            if isEditing {
-                if let indexLM = listMentionInTextField.firstIndex(where: { $0.fullName.trimmingCharacters(in: .whitespaces).contains(username) }) {
-                    let fullNameText = listMentionInTextField[indexLM].fullName.trimmingCharacters(in: .whitespaces)
-                    let endRange = range.lowerBound + fullNameText.count
-                    if endRange <= text.string.count {
-                        let fullRange = match.range(at: 0)
-                        if fullRange.lowerBound == ((Int(listMentionInTextField[indexLM].ex_block ?? "0") ?? 0) - fullNameText.count){
-                            text.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: fullRange.lowerBound, length: 1))
-                            text.addAttribute(.foregroundColor, value: UIColor.mentionColor, range: NSRange(location: range.lowerBound, length: fullNameText.count))
-                            text.addAttribute(.font, value: UIFont.systemFont(ofSize: 12 + String.offset(), weight: .medium), range: NSRange(location: fullRange.lowerBound, length: fullNameText.count + 1))
-                        }
+            if isEditing && listMentionInTextField.count > 0 {
+                for mention in listMentionInTextField {
+                    let upper = (Int(mention.ex_block ?? "0") ?? 0)
+                    let lower = upper - mention.fullName.count
+                    if lower >= 0 {
+                        text.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: lower, length: 1))
+                        text.addAttribute(.foregroundColor, value: UIColor.mentionColor, range: NSRange(location: lower + 1, length: mention.fullName.count))
+                        text.addAttribute(.font, value: UIFont.systemFont(ofSize: 12 + String.offset(), weight: .medium), range: NSRange(location: lower, length: mention.fullName.count))
                     }
                 }
             } else {
@@ -1271,14 +1268,14 @@ public class ImageCache {
             return
         }
 
-        for (originalKey, sanitizedKey) in cacheKeyMap {
+        for (_, sanitizedKey) in cacheKeyMap {
             if let image = cache.object(forKey: sanitizedKey as NSString),
                let imageData = image.pngData() {
-                let filePath = directory.appendingPathComponent("\(sanitizedKey).png")
                 do {
-                    try imageData.write(to: filePath)
+                    try FileEncryption.shared.writeSecure(filename: "\(sanitizedKey).png", data: imageData)
                 } catch {
                 }
+                
             }
         }
     }
@@ -1304,9 +1301,22 @@ public class ImageCache {
                 if fileURL.lastPathComponent == "keyMapping.json" { continue } // Skip mapping file
 
                 let sanitizedKey = fileURL.deletingPathExtension().lastPathComponent
-                if let imageData = try? Data(contentsOf: fileURL),
-                   let image = UIImage(data: imageData) {
-                    cache.setObject(image, forKey: sanitizedKey as NSString)
+                let imageName = "\(sanitizedKey).png"
+                if FileEncryption.shared.isSecureExists(filename: imageName) {
+                    do {
+                        if var data = try FileEncryption.shared.readSecure(filename: imageName) {
+                            let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
+                            if dataDecrypt != nil {
+                                data = dataDecrypt!
+                            }
+                            if let image = UIImage(data: data) {
+                                cache.setObject(image, forKey: sanitizedKey as NSString)
+                            }
+                        }
+                    }
+                    catch {
+                        print("Error reading secure file")
+                    }
                 }
             }
         } catch {

+ 10 - 10
NexilisLite/NexilisLite/Source/IncomingThread.swift

@@ -1337,17 +1337,17 @@ class IncomingThread {
             } catch {
             }
         }
-        if (!thumb_id.isEmpty && media.count == 0) {
-            Download().startHTTP(forKey: thumb_id) { (file, progress) in
-                //print ("masuk download \(progress)")
-                if(progress == 100 || progress == -100) {
-                    Nexilis.saveMessage(message: message, withStatus: false)
-                    //print("save message incoming")
-                }
-            }
-        } else {
+//        if (!thumb_id.isEmpty && media.count == 0) {
+//            Download().startHTTP(forKey: thumb_id) { (file, progress) in
+//                //print ("masuk download \(progress)")
+//                if(progress == 100 || progress == -100) {
+//                    Nexilis.saveMessage(message: message, withStatus: false)
+//                    //print("save message incoming")
+//                }
+//            }
+//        } else {
             Nexilis.saveMessage(message: message, withStatus: false)
-        }
+//        }
 //        DispatchQueue.main.async { [self] in
 //            if APIS.checkAppStateisBackground() {
 //                APIS.addNotificationNexilis(message)

+ 21 - 21
NexilisLite/NexilisLite/Source/Network.swift

@@ -8,6 +8,7 @@
 
 import Foundation
 import Alamofire
+import UniformTypeIdentifiers
 
 public class Network {
     let uploadGroup = DispatchGroup()
@@ -201,24 +202,12 @@ public class Network {
             do {
                 let fileManager = FileManager.default
                 let documentDir = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
-                let tempDir = documentDir.appendingPathComponent("temp")
-                if !fileManager.fileExists(atPath: tempDir.path) {
-                    do {
-                        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true, attributes: nil)
-                    } catch {
-                        print("Error creating directory: \(error)")
-                    }
-                }
                 for name in filename {
                     let fileDir = documentDir.appendingPathComponent(name)
                     let path = fileDir.path
                     if FileManager.default.fileExists(atPath: path) {
                         let fileURL = URL(fileURLWithPath: path)
-                        let filenameServer = "\(name)"
-                        let fileDirServer = tempDir.appendingPathComponent(filenameServer)
-                        let fileURLServer = URL(fileURLWithPath: fileDirServer.path)
                         filesIn.append(fileURL)
-                        filesTempServer.append(fileURLServer)
                     }
                 }
             }
@@ -254,12 +243,20 @@ public class Network {
             }
             
             for i in 0..<filesIn.count {
-                multipartFormData.append(filesIn[i], withName: "file\(i+1)", fileName: filesIn[i].lastPathComponent, mimeType: "application/octet-stream")
+                let mime = self.mimeType(for: filesIn[i])
+                multipartFormData.append(filesIn[i], withName: "file\(i+1)", fileName: filesIn[i].lastPathComponent, mimeType: mime)
                 Nexilis.putUploadFile(forKey: filesIn[i].lastPathComponent, uploader: self)
                 //print(multipartFormData)
             }
             
         }, to: endUrl, headers: headers)
+        .uploadProgress { progress in
+//            print("Response progress: \(progress.fractionCompleted*100)")
+            let frac = progress.fractionCompleted*100
+            if frac != 100.0 {
+                completion(!progress.isCancelled,frac)
+            }
+        }
         .responseJSON { result in
             if let response = result.response, response.statusCode == 200 {
                 //print("Response success")
@@ -271,21 +268,24 @@ public class Network {
             }
             else {
                 let statusCode = result.response?.statusCode
-                print("Response fail: \(statusCode) <><> \(result)")
+//                print("Response fail: \(statusCode) <><> \(result)")
                 completion(false,0)
             }
         }
-        .uploadProgress { progress in
-            //print("Response progress: \(progress.fractionCompleted*100)")
-            let frac = progress.fractionCompleted*100
-            if frac != 100.0 {
-                completion(!progress.isCancelled,frac)
-            }
-        }
         
         return uploadRequest
     }
     
+    func mimeType(for url: URL) -> String {
+        if #available(iOS 14.0, *) {
+            if let utType = UTType(filenameExtension: url.pathExtension),
+               let mimeType = utType.preferredMIMEType {
+                return mimeType
+            }
+        }
+        return "application/octet-stream"
+    }
+    
     public func cancel() {
         self.isCancel = true
     }

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

@@ -232,7 +232,7 @@ public class Nexilis: NSObject {
                                     do {
                                         let documentDir = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
                                         let file = documentDir.appendingPathComponent(cursorData.string(forColumnIndex: 0)!)
-                                        if !FileManager().fileExists(atPath: file.path) || !FileEncryption.shared.isSecureExists(filename: cursorData.string(forColumnIndex: 0)!) {
+                                        if !FileManager().fileExists(atPath: file.path) && !FileEncryption.shared.isSecureExists(filename: cursorData.string(forColumnIndex: 0)!) {
                                             Download().startHTTP(forKey: cursorData.string(forColumnIndex: 0)!) { (name, progress) in}
                                         }
                                     } catch {}
@@ -2888,7 +2888,7 @@ extension Nexilis: MessageDelegate {
                                         
                                     }
                                 } else {
-                                    Download().startHTTP(forKey: file, isImage: false) { (name, progress) in
+                                    Download().startHTTP(forKey: file) { (name, progress) in
                                         DispatchQueue.main.async {
                                             guard progress == 100 else {
                                                 return

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

@@ -1097,7 +1097,7 @@ class QmeraAudioViewController: UIViewController {
                         countLoop = countLoop + 1
                         if countLoop == 3 {
                             DispatchQueue.main.async {
-                                if !self.speaker.isEnabled{
+                                if !self.speaker.isEnabled {
                                     self.speaker.isEnabled = true
                                 }
                             }

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

@@ -648,7 +648,7 @@ public class ChatWALikeVC: UIViewController, UITableViewDataSource, UITableViewD
                 if let dirPath = paths.first {
                     let audioURL = URL(fileURLWithPath: dirPath).appendingPathComponent(data.audio)
                     if !FileManager.default.fileExists(atPath: audioURL.path) && !FileEncryption.shared.isSecureExists(filename: data.audio) {
-                        Download().startHTTP(forKey: data.audio, isImage: false) { (name, progress) in
+                        Download().startHTTP(forKey: data.audio) { (name, progress) in
                             guard progress == 100 else {
                                 return
                             }

+ 79 - 39
NexilisLite/NexilisLite/Source/View/Chat/EditorGroup.swift

@@ -121,6 +121,8 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
     var playingIndexPath: IndexPath?
     var timerSearch: Timer?
     
+    var downloadList: [String: IndexPath] = [:]
+    
     var tableMentionEdit = UITableView()
     var heightTableEditMention: NSLayoutConstraint!
     
@@ -751,7 +753,7 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
                         }
                         row["chat_date"] = chatDate(stringDate: row["server_date"]  as? String ?? "")
                         
-                        if (dataMessages.count == 0 || dataMessages.last!["f_pin"]  as? String ?? "" == row["f_pin"]  as? String ?? "") && tempImages.count <= 30 && row["image_id"] != nil && !(row["image_id"]  as? String ?? "").isEmpty && (row["message_text"]  as? String ?? "").isEmpty && (row["reff_id"]  as? String ?? "").isEmpty && (row["read_receipts"]  as? String ?? "") != "8" {
+                        if (dataMessages.count == 0 || dataMessages.last!["f_pin"]  as? String ?? "" == row["f_pin"]  as? String ?? "") && tempImages.count <= 30 && row["image_id"] != nil && !(row["image_id"] as? String ?? "").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && (row["message_text"] as? String ?? "").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && (row["reff_id"] as? String ?? "").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && (row["read_receipts"] as? String ?? "") != "8" {
                             if tempImages.count != 0 && getSecondsDifferenceFromTwoDates(start: Date.init(milliseconds: Int64(tempImages.last!.time)!), end: Date.init(milliseconds: Int64(row["server_date"]  as? String ?? "")!))/60 >= 11 {
                                 if tempImages.count >= 4 {
                                     groupImages[tempImages[0].messageId] = tempImages
@@ -1468,6 +1470,7 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
             case "image":
                 var config = PHPickerConfiguration()
                 config.filter = .images
+                config.preferredAssetRepresentationMode = .current
                 let picker = PHPickerViewController(configuration: config)
                 picker.delegate = self
                 if UIBarButtonItem.appearance().titleTextAttributes(for: .normal) != nil {
@@ -1481,6 +1484,7 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
             case "video":
                 var config = PHPickerConfiguration()
                 config.filter = .videos
+                config.preferredAssetRepresentationMode = .current
                 let picker = PHPickerViewController(configuration: config)
                 picker.delegate = self
                 if UIBarButtonItem.appearance().titleTextAttributes(for: .normal) != nil {
@@ -2168,7 +2172,7 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
                         }
                     }
                 } else if dataMessages[index]["video_id"] as? String != nil && !((dataMessages[index]["video_id"] as? String)!.isEmpty){
-                    Download().startHTTP(forKey: dataMessages[index]["video_id"]  as? String ?? "", isImage: false) { (name, progress) in
+                    Download().startHTTP(forKey: dataMessages[index]["video_id"]  as? String ?? "") { (name, progress) in
                         guard progress == 100 else {
                             return
                         }
@@ -2222,7 +2226,7 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
                     }
                 }
                 else if dataMessages[index]["file_id"] as? String != nil && !((dataMessages[index]["file_id"] as? String)!.isEmpty) {
-                    Download().startHTTP(forKey: dataMessages[index]["file_id"]  as? String ?? "", isImage: false) { (name, progress) in
+                    Download().startHTTP(forKey: dataMessages[index]["file_id"]  as? String ?? "") { (name, progress) in
                         guard progress == 100 else {
                             return
                         }
@@ -2377,23 +2381,30 @@ extension EditorGroup: ImageVideoPickerDelegate, PreviewAttachmentImageVideoDele
         } else if result.itemProvider.hasItemConformingToTypeIdentifier("public.image") {
             picker.dismiss(animated: true, completion: {
                 Nexilis.showLoader()
-                result.itemProvider.loadObject(ofClass: UIImage.self) { object, error in
-                    if let image = object as? UIImage {
-                        DispatchQueue.main.async {
-                            Nexilis.hideLoader {
-                                let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
-                                if (self.textFieldSend.textColor != .lightGray) {
-                                    previewImageVC.currentTextTextField = self.textFieldSend.text
+                result.itemProvider.loadFileRepresentation(forTypeIdentifier: "public.image") { url, error in
+                    if let url = url {
+                        do {
+                            let data = try Data(contentsOf: url)
+                            DispatchQueue.main.async {
+                                Nexilis.hideLoader {
+                                    let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
+                                    if (self.textFieldSend.textColor != .lightGray) {
+                                        previewImageVC.currentTextTextField = self.textFieldSend.text
+                                    }
+                                    previewImageVC.fromCopy = true
+                                    previewImageVC.image = UIImage(data: data)
+                                    previewImageVC.modalPresentationStyle = .custom
+                                    previewImageVC.delegate = self
+                                    previewImageVC.isAck = self.isAck
+                                    previewImageVC.isConfidential = self.isConfidential
+                                    self.present(previewImageVC, animated: true, completion: nil)
                                 }
-                                previewImageVC.fromCopy = true
-                                previewImageVC.image = image
-                                previewImageVC.modalPresentationStyle = .custom
-                                previewImageVC.delegate = self
-                                previewImageVC.isAck = self.isAck
-                                previewImageVC.isConfidential = self.isConfidential
-                                self.present(previewImageVC, animated: true, completion: nil)
                             }
+                        } catch {
+                            print("Error loading image data: \(error)")
                         }
+                    } else {
+                        print("Error: \(String(describing: error))")
                     }
                 }
             })
@@ -3507,8 +3518,8 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
                                     fixUser = user
                                 }
                                 var indexAt = 0
-                                if let range = oldText.range(of: result) {
-                                    indexAt = oldText.distance(from: oldText.startIndex, to: range.lowerBound)
+                                if let range = oldTextForTextview.range(of: result) {
+                                    indexAt = oldTextForTextview.distance(from: oldTextForTextview.startIndex, to: range.lowerBound)
                                 }
                                 fixUser?.ex_block = "\(indexAt + fixUser!.fullName.count)"
                                 listMentionWithText.append(fixUser!)
@@ -3933,7 +3944,7 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
                 formatterTime.dateFormat = "HH:mm"
                 formatterTime.locale = NSLocale(localeIdentifier: "id") as Locale?
                 let dataProfile = getDataProfile(f_pin: dataMessages[i]["f_pin"]  as? String ?? "", message_id: dataMessages[i]["message_id"]  as? String ?? "")
-                let textCopied = (dataMessages[i]["message_text"]  as? String ?? "").richText(isEditing: true, group_id: self.dataGroup["group_id"]  as? String ?? "", listMentionInTextField: listMentionInTextField)
+                let textCopied = (dataMessages[i]["message_text"]  as? String ?? "").richText(isEditing: true, group_id: self.dataGroup["group_id"]  as? String ?? "")
                 text = text + "\n\n*[\(formatterDate.string(from: date as Date)) \(formatterTime.string(from: date as Date))] \(dataProfile["name"]!):*\n\(textCopied.string)"
             }
             text = text + "\n\n\nchat " + "Powered by Nexilis".localized()
@@ -4425,8 +4436,8 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                 }
             }
         }
+        let dataMessages = self.dataMessages.filter({ $0["chat_date"]  as? String ?? "" == dataDates[indexPath.section] })
         if copySession || forwardSession || deleteSession {
-            let dataMessages = self.dataMessages.filter({ $0["chat_date"]  as? String ?? "" == dataDates[indexPath.section] })
             guard indexPath.row < dataMessages.count else {
                 return
             }
@@ -4857,7 +4868,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
             imageAckView.image = imageAck
         }
         
-        if (dataMessages[indexPath.row]["credential"] as? String) == "1" && (dataMessages[indexPath.row]["lock"] as? String) != "2" {
+        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
@@ -5033,7 +5044,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
         
         let stringDate = (dataMessages[indexPath.row]["server_date"]  as? String ?? "")
         if !stringDate.isEmpty {
-            if (dataMessages[indexPath.row]["credential"] as? String) == "1" && dataMessages[indexPath.row]["lock"] as? String != "2" {
+            if (dataMessages[indexPath.row]["credential"] as? String) == "1" && dataMessages[indexPath.row]["lock"] as? String != "2"  && dataMessages[indexPath.row]["lock"] as? String != "1" {
                 if dataTimer! >= 10 {
                     timeMessage.text = "00:\(dataTimer!)"
                 } else {
@@ -5107,7 +5118,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                         activityIndicator.centerXAnchor.constraint(equalTo: playButtonAudio.centerXAnchor),
                         activityIndicator.centerYAnchor.constraint(equalTo: playButtonAudio.centerYAnchor)
                     ])
-                    Download().startHTTP(forKey: audioChat, isImage: false) { (name, progress) in
+                    Download().startHTTP(forKey: audioChat) { (name, progress) in
                         guard progress == 100 else {
                             return
                         }
@@ -5165,7 +5176,12 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                 timeMessage.isHidden = true
                 statusMessage.isHidden = true
                 imageStared.isHidden = true
-                topMarginText.constant = topMarginText.constant + 205
+                topMarginText.constant = topMarginText.constant + 220
+                var constTop = 35.0
+                if dataMessages[indexPath.row][TypeDataMessage.is_forwarded] != nil && dataMessages[indexPath.row][TypeDataMessage.is_forwarded] as! Int != 0 {
+                    topMarginText.constant = topMarginText.constant + 20
+                    constTop = 55.0
+                }
                 let listImageThumb: [UIImageView] = [UIImageView(), UIImageView(), UIImageView(), UIImageView()]
                 for i in 0..<4 {
                     containerMessage.addSubview(listImageThumb[i])
@@ -5175,9 +5191,9 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                     let widthHeightImage: CGFloat = 120
                     switch i {
                     case 0:
-                        listImageThumb[i].anchor(top: containerMessage.topAnchor, left: containerMessage.leftAnchor, paddingTop: 5, paddingLeft: 5, width: widthHeightImage, height: widthHeightImage)
+                        listImageThumb[i].anchor(top: containerMessage.topAnchor, left: containerMessage.leftAnchor, paddingTop: constTop, paddingLeft: 5, width: widthHeightImage, height: widthHeightImage)
                     case 1:
-                        listImageThumb[i].anchor(top: containerMessage.topAnchor, left: listImageThumb[0].rightAnchor, right: containerMessage.rightAnchor, paddingTop: 5, paddingLeft: 5, paddingRight: 5, width: widthHeightImage, height: widthHeightImage)
+                        listImageThumb[i].anchor(top: containerMessage.topAnchor, left: listImageThumb[0].rightAnchor, right: containerMessage.rightAnchor, paddingTop: constTop, paddingLeft: 5, paddingRight: 5, width: widthHeightImage, height: widthHeightImage)
                     case 2:
                         listImageThumb[i].anchor(left: containerMessage.leftAnchor, bottom: containerMessage.bottomAnchor, paddingLeft: 5, paddingBottom: 5, width: widthHeightImage, height: widthHeightImage)
                     default:
@@ -5191,16 +5207,16 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                         if FileManager.default.fileExists(atPath: thumbURL.path) {
                             DispatchQueue.main.async {
                                 let image : UIImage? =  {
-                                    if let img = Nexilis.imageCache.object(forKey: thumbChat as NSString) {
+                                    if let img = Nexilis.imageCache.object(forKey: listImages[i].thumbId as NSString) {
                                         return img
                                     }
                                     else if let img = UIImage(contentsOfFile: thumbURL.path)?.resize(target: CGSize(width: 500, height: 500)) {
-                                            Nexilis.imageCache.setObject(img, forKey: thumbChat as NSString)
+                                            Nexilis.imageCache.setObject(img, forKey: listImages[i].thumbId as NSString)
                                             return img
                                     }
                                     return nil
                                 }()
-                                imageThumb.image = image
+                                listImageThumb[i].image = image
                             }
                         } else if FileEncryption.shared.isSecureExists(filename: listImages[i].thumbId) {
                             do {
@@ -5220,12 +5236,19 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                                             }
                                             return nil
                                         }()
-                                        imageThumb.image = image
+                                        listImageThumb[i].image = image
                                     }
                                 }
                             } catch {
                                 
                             }
+                        } else {
+                            Download().startHTTP(forKey: listImages[i].thumbId) { (name, progress) in
+                                guard progress == 100 else {
+                                    return
+                                }
+                                tableView.reloadRows(at: [indexPath], with: .none)
+                            }
                         }
                         
                         let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(listImages[i].imageId)
@@ -5373,6 +5396,13 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                         } catch {
                             
                         }
+                    } else {
+                        Download().startHTTP(forKey: thumbChat) { (name, progress) in
+                            guard progress == 100 else {
+                                return
+                            }
+                            tableView.reloadRows(at: [indexPath], with: .none)
+                        }
                     }
                     
                     let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(imageChat)
@@ -5410,7 +5440,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                     if let dirPath = paths.first {
                         let gifURL = URL(fileURLWithPath: dirPath).appendingPathComponent(gifChat)
                         if !FileManager.default.fileExists(atPath: gifURL.path) && !FileEncryption.shared.isSecureExists(filename: gifChat) {
-                            Download().startHTTP(forKey: gifChat, isImage: false) { (name, progress) in
+                            Download().startHTTP(forKey: gifChat) { (name, progress) in
                                 guard progress == 100 else {
                                     return
                                 }
@@ -5988,7 +6018,9 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
             containerForwarded.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
             containerForwarded.heightAnchor.constraint(equalToConstant: 20).isActive = true
             if thumbChat != "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"]  as? String ?? "" != "1") {
-                containerForwarded.bottomAnchor.constraint(equalTo: imageThumb.topAnchor, constant: -5).isActive = true
+                if groupImages[messageIdChat] == nil {
+                    containerForwarded.bottomAnchor.constraint(equalTo: imageThumb.topAnchor, constant: -5).isActive = true
+                }
             } else if fileChat != "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"]  as? String ?? "" != "1") {
                 containerForwarded.bottomAnchor.constraint(equalTo: containerViewFile.topAnchor, constant: -5).isActive = true
             } else if containerMessage.subviews.contains(containerLinkMessage) {
@@ -6353,6 +6385,10 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                         
                     }
                 } else {
+                    if downloadList[sender.video_id] != nil && downloadList[sender.video_id] == sender.indexPath {
+                        return
+                    }
+                    downloadList[sender.video_id] = sender.indexPath
                     for view in sender.imageView.subviews {
                         if view is UIImageView {
                             view.removeFromSuperview()
@@ -6370,7 +6406,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                     trackShape.path = circlePath.cgPath
                     trackShape.fillColor = UIColor.clear.cgColor
                     trackShape.lineWidth = 10
-                    trackShape.strokeColor = UIColor.blueBubbleColor.withAlphaComponent(0.3).cgColor
+                    trackShape.strokeColor = UIColor.mentionColor.withAlphaComponent(0.3).cgColor
                     container.backgroundColor = .clear
                     container.layer.addSublayer(trackShape)
                     let shapeLoading = CAShapeLayer()
@@ -6378,7 +6414,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                     shapeLoading.fillColor = UIColor.clear.cgColor
                     shapeLoading.lineWidth = 10
                     shapeLoading.strokeEnd = 0
-                    shapeLoading.strokeColor = UIColor.blueBubbleColor.cgColor
+                    shapeLoading.strokeColor = UIColor.mentionColor.cgColor
                     container.layer.addSublayer(shapeLoading)
                     let imageDownload = UIImageView(image: UIImage(systemName: "arrow.down", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
                     imageDownload.tintColor = .white
@@ -6388,7 +6424,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                     imageDownload.centerYAnchor.constraint(equalTo: sender.imageView.centerYAnchor).isActive = true
                     imageDownload.widthAnchor.constraint(equalToConstant: 30).isActive = true
                     imageDownload.heightAnchor.constraint(equalToConstant: 30).isActive = true
-                    Download().startHTTP(forKey: sender.video_id, isImage: false) { (name, progress) in
+                    Download().startHTTP(forKey: sender.video_id) { (name, progress) in
                         DispatchQueue.main.async {
                             guard progress == 100 else {
                                 shapeLoading.strokeEnd = CGFloat(progress / 100)
@@ -6472,6 +6508,10 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                         
                     }
                 } 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()
@@ -6490,14 +6530,14 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                     trackShape.path = circlePath.cgPath
                     trackShape.fillColor = UIColor.clear.cgColor
                     trackShape.lineWidth = 5
-                    trackShape.strokeColor = UIColor.blueBubbleColor.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.blueBubbleColor.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
@@ -6506,7 +6546,7 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                     imageupload.centerYAnchor.constraint(equalTo: containerLoading.centerYAnchor).isActive = true
                     imageupload.centerXAnchor.constraint(equalTo: containerLoading.centerXAnchor).isActive = true
                     
-                    Download().startHTTP(forKey: sender.file_id, isImage: false) { (name, progress) in
+                    Download().startHTTP(forKey: sender.file_id) { (name, progress) in
                         DispatchQueue.main.async {
                             guard progress == 100 else {
                                 shapeLoading.strokeEnd = CGFloat(progress / 100)

+ 85 - 45
NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift

@@ -131,6 +131,8 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
     var playingIndexPath: IndexPath?
     var timerSearch: Timer?
     
+    var downloadList: [String: IndexPath] = [:]
+    
     func offset() -> CGFloat{
         guard let fontSize = Int(SecureUserDefaults.shared.value(forKey: "font_size") ?? "0") else { return 0 }
         return CGFloat(fontSize)
@@ -572,7 +574,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
             chatbot()
         }
         
-        let exblock = User.getData(pin: self.dataPerson["f_pin"]!!)?.ex_block
+        let exblock = User.getData(pin: pinPerson)?.ex_block
         blocking = exblock == nil ? "0" : exblock!.isEmpty ? "0" : exblock!
         
         changeAppBar()
@@ -1235,7 +1237,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                                 }
                             }
                         }
-                        if (dataMessages.count == 0 || dataMessages.last!["f_pin"]  as? String ?? "" == row["f_pin"]  as? String ?? "") && tempImages.count <= 30 && row["image_id"] != nil && !(row["image_id"]  as? String ?? "").isEmpty && (row["message_text"]  as? String ?? "").isEmpty && (row["reff_id"]  as? String ?? "").isEmpty && (row["credential"]  as? String ?? "") != "1" && (row["read_receipts"]  as? String ?? "") != "8" {
+                        if (dataMessages.count == 0 || dataMessages.last!["f_pin"]  as? String ?? "" == row["f_pin"]  as? String ?? "") && tempImages.count <= 30 && row["image_id"] != nil && !(row["image_id"] as? String ?? "").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && (row["message_text"] as? String ?? "").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && (row["reff_id"] as? String ?? "").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && (row["credential"] as? String ?? "") != "1" && (row["read_receipts"] as? String ?? "") != "8" {
                             if tempImages.count != 0 && getSecondsDifferenceFromTwoDates(start: Date.init(milliseconds: Int64(tempImages.last!.time)!), end: Date.init(milliseconds: Int64(row["server_date"]  as? String ?? "")!))/60 >= 11 {
                                 if tempImages.count >= 4 {
                                     groupImages[tempImages[0].messageId] = tempImages
@@ -2196,6 +2198,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
             case "image":
                 var config = PHPickerConfiguration()
                 config.filter = .images
+                config.preferredAssetRepresentationMode = .current
                 let picker = PHPickerViewController(configuration: config)
                 picker.delegate = self
                 if UIBarButtonItem.appearance().titleTextAttributes(for: .normal) != nil {
@@ -2209,6 +2212,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
             case "video":
                 var config = PHPickerConfiguration()
                 config.filter = .videos
+                config.preferredAssetRepresentationMode = .current
                 let picker = PHPickerViewController(configuration: config)
                 picker.delegate = self
                 if UIBarButtonItem.appearance().titleTextAttributes(for: .normal) != nil {
@@ -3397,7 +3401,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                         }
                     }
                 } else if dataMessages[index]["video_id"] as? String != nil && !((dataMessages[index]["video_id"] as? String)!.isEmpty){
-                    Download().startHTTP(forKey: dataMessages[index]["video_id"]  as? String ?? "", isImage: false) { (name, progress) in
+                    Download().startHTTP(forKey: dataMessages[index]["video_id"]  as? String ?? "") { (name, progress) in
                         guard progress == 100 else {
                             return
                         }
@@ -3450,7 +3454,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                     }
                 }
                 else if dataMessages[index]["file_id"] as? String != nil && !((dataMessages[index]["file_id"] as? String)!.isEmpty) {
-                    Download().startHTTP(forKey: dataMessages[index]["file_id"]  as? String ?? "", isImage: false) { (name, progress) in
+                    Download().startHTTP(forKey: dataMessages[index]["file_id"]  as? String ?? "") { (name, progress) in
                         guard progress == 100 else {
                             return
                         }
@@ -3707,23 +3711,30 @@ extension EditorPersonal: PreviewAttachmentImageVideoDelegate, PHPickerViewContr
         } else if result.itemProvider.hasItemConformingToTypeIdentifier("public.image") {
             picker.dismiss(animated: true, completion: {
                 Nexilis.showLoader()
-                result.itemProvider.loadObject(ofClass: UIImage.self) { object, error in
-                    if let image = object as? UIImage {
-                        DispatchQueue.main.async {
-                            Nexilis.hideLoader {
-                                let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
-                                if (self.textFieldSend.textColor != .lightGray) {
-                                    previewImageVC.currentTextTextField = self.textFieldSend.text
+                result.itemProvider.loadFileRepresentation(forTypeIdentifier: "public.image") { url, error in
+                    if let url = url {
+                        do {
+                            let data = try Data(contentsOf: url)
+                            DispatchQueue.main.async {
+                                Nexilis.hideLoader {
+                                    let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
+                                    if (self.textFieldSend.textColor != .lightGray) {
+                                        previewImageVC.currentTextTextField = self.textFieldSend.text
+                                    }
+                                    previewImageVC.fromCopy = true
+                                    previewImageVC.image = UIImage(data: data)
+                                    previewImageVC.modalPresentationStyle = .custom
+                                    previewImageVC.delegate = self
+                                    previewImageVC.isAck = self.isAck
+                                    previewImageVC.isConfidential = self.isConfidential
+                                    self.present(previewImageVC, animated: true, completion: nil)
                                 }
-                                previewImageVC.fromCopy = true
-                                previewImageVC.image = image
-                                previewImageVC.modalPresentationStyle = .custom
-                                previewImageVC.delegate = self
-                                previewImageVC.isAck = self.isAck
-                                previewImageVC.isConfidential = self.isConfidential
-                                self.present(previewImageVC, animated: true, completion: nil)
                             }
+                        } catch {
+                            print("Error loading image data: \(error)")
                         }
+                    } else {
+                        print("Error: \(String(describing: error))")
                     }
                 }
             })
@@ -5474,8 +5485,8 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
         if isContactCenter && indexPath.row == 0 && isRequestContactCenter {
             return
         }
+        let dataMessages = self.dataMessages.filter({ $0["chat_date"]  as? String ?? "" == dataDates[indexPath.section] })
         if copySession || forwardSession || deleteSession {
-            let dataMessages = self.dataMessages.filter({ $0["chat_date"]  as? String ?? "" == dataDates[indexPath.section] })
             guard indexPath.row < dataMessages.count else {
                 return
             }
@@ -6113,7 +6124,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
             }
         }
         
-        if (dataMessages[indexPath.row]["credential"] as? String) == "1" && (dataMessages[indexPath.row]["lock"] as? String) != "2" {
+        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
@@ -6554,7 +6565,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                         activityIndicator.centerXAnchor.constraint(equalTo: playButtonAudio.centerXAnchor),
                         activityIndicator.centerYAnchor.constraint(equalTo: playButtonAudio.centerYAnchor)
                     ])
-                    Download().startHTTP(forKey: audioChat, isImage: false) { (name, progress) in
+                    Download().startHTTP(forKey: audioChat) { (name, progress) in
                         guard progress == 100 else {
                             return
                         }
@@ -6612,7 +6623,12 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                 timeMessage.isHidden = true
                 statusMessage.isHidden = true
                 imageStared.isHidden = true
-                topMarginText.constant = topMarginText.constant + 225
+                topMarginText.constant = topMarginText.constant + 205
+                var constTop = 5.0
+                if dataMessages[indexPath.row][TypeDataMessage.is_forwarded] != nil && dataMessages[indexPath.row][TypeDataMessage.is_forwarded] as! Int != 0 {
+                    topMarginText.constant = topMarginText.constant + 10
+                    constTop = 35.0
+                }
                 let listImageThumb: [UIImageView] = [UIImageView(), UIImageView(), UIImageView(), UIImageView()]
                 for i in 0..<4 {
                     containerMessage.addSubview(listImageThumb[i])
@@ -6622,9 +6638,9 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                     let widthHeightImage: CGFloat = 120
                     switch i {
                         case 0:
-                            listImageThumb[i].anchor(top: containerMessage.topAnchor, left: containerMessage.leftAnchor, paddingTop: 5, paddingLeft: 5, width: widthHeightImage, height: widthHeightImage)
+                            listImageThumb[i].anchor(top: containerMessage.topAnchor, left: containerMessage.leftAnchor, paddingTop: constTop, paddingLeft: 5, width: widthHeightImage, height: widthHeightImage)
                         case 1:
-                            listImageThumb[i].anchor(top: containerMessage.topAnchor, left: listImageThumb[0].rightAnchor, right: containerMessage.rightAnchor, paddingTop: 5, paddingLeft: 5, paddingRight: 5, width: widthHeightImage, height: widthHeightImage)
+                            listImageThumb[i].anchor(top: containerMessage.topAnchor, left: listImageThumb[0].rightAnchor, right: containerMessage.rightAnchor, paddingTop: constTop, paddingLeft: 5, paddingRight: 5, width: widthHeightImage, height: widthHeightImage)
                         case 2:
                             listImageThumb[i].anchor(left: containerMessage.leftAnchor, bottom: containerMessage.bottomAnchor, paddingLeft: 5, paddingBottom: 5, width: widthHeightImage, height: widthHeightImage)
                         default:
@@ -6638,16 +6654,16 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                         if FileManager.default.fileExists(atPath: thumbURL.path) {
                             DispatchQueue.main.async {
                                 let image : UIImage? =  {
-                                    if let img = Nexilis.imageCache.object(forKey: thumbChat as NSString) {
+                                    if let img = Nexilis.imageCache.object(forKey: listImages[i].thumbId as NSString) {
                                         return img
                                     }
                                     else if let img = UIImage(contentsOfFile: thumbURL.path)?.resize(target: CGSize(width: 500, height: 500)) {
-                                            Nexilis.imageCache.setObject(img, forKey: thumbChat as NSString)
+                                            Nexilis.imageCache.setObject(img, forKey: listImages[i].thumbId as NSString)
                                             return img
                                     }
                                     return nil
                                 }()
-                                imageThumb.image = image
+                                listImageThumb[i].image = image
                             }
                         } else if FileEncryption.shared.isSecureExists(filename: listImages[i].thumbId) {
                             do {
@@ -6667,12 +6683,19 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                                             }
                                             return nil
                                         }()
-                                        imageThumb.image = image
+                                        listImageThumb[i].image = image
                                     }
                                 }
                             } catch {
                                 
                             }
+                        } else {
+                            Download().startHTTP(forKey: listImages[i].thumbId) { (name, progress) in
+                                guard progress == 100 else {
+                                    return
+                                }
+                                tableView.reloadRows(at: [indexPath], with: .none)
+                            }
                         }
 
                         let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(listImages[i].imageId)
@@ -6820,6 +6843,13 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                         } catch {
                             
                         }
+                    } else {
+                        Download().startHTTP(forKey: thumbChat) { (name, progress) in
+                            guard progress == 100 else {
+                                return
+                            }
+                            tableView.reloadRows(at: [indexPath], with: .none)
+                        }
                     }
                     let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(imageChat)
                     if !FileManager.default.fileExists(atPath: imageURL.path) && !FileEncryption.shared.isSecureExists(filename: imageURL.lastPathComponent) {
@@ -6855,7 +6885,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                     if let dirPath = paths.first {
                         let gifURL = URL(fileURLWithPath: dirPath).appendingPathComponent(gifChat)
                         if !FileManager.default.fileExists(atPath: gifURL.path) && !FileEncryption.shared.isSecureExists(filename: gifChat) {
-                            Download().startHTTP(forKey: gifChat, isImage: false) { (name, progress) in
+                            Download().startHTTP(forKey: gifChat) { (name, progress) in
                                 guard progress == 100 else {
                                     return
                                 }
@@ -6905,7 +6935,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                     trackShape.path = circlePath.cgPath
                     trackShape.fillColor = UIColor.black.withAlphaComponent(0.3).cgColor
                     trackShape.lineWidth = 3
-                    trackShape.strokeColor = UIColor.blueBubbleColor.withAlphaComponent(0.3).cgColor
+                    trackShape.strokeColor = UIColor.mentionColor.withAlphaComponent(0.3).cgColor
                     container.backgroundColor = .clear
                     container.layer.addSublayer(trackShape)
                     let shapeLoading = CAShapeLayer()
@@ -6913,7 +6943,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                     shapeLoading.fillColor = UIColor.clear.cgColor
                     shapeLoading.lineWidth = 3
                     shapeLoading.strokeEnd = 0
-                    shapeLoading.strokeColor = UIColor.blueBubbleColor.cgColor
+                    shapeLoading.strokeColor = UIColor.mentionColor.cgColor
                     container.layer.addSublayer(shapeLoading)
                     let imageupload = UIImageView(image: UIImage(systemName: "arrow.up", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
                     imageupload.tintColor = .white
@@ -7040,14 +7070,14 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                 trackShape.path = circlePath.cgPath
                 trackShape.fillColor = UIColor.clear.cgColor
                 trackShape.lineWidth = 5
-                trackShape.strokeColor = UIColor.blueBubbleColor.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.secondaryColor.cgColor
+                shapeLoading.strokeColor = UIColor.mentionColor.cgColor
                 containerLoading.layer.addSublayer(shapeLoading)
                 var imageupload = UIImageView(image: UIImage(systemName: "arrow.up", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
                 if dataMessages[indexPath.row]["f_pin"] as? String != idMe {
@@ -7418,7 +7448,9 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
             containerForwarded.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
             containerForwarded.heightAnchor.constraint(equalToConstant: 20).isActive = true
             if thumbChat != "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"]  as? String ?? "" != "1") {
-                containerForwarded.bottomAnchor.constraint(equalTo: imageThumb.topAnchor, constant: -5).isActive = true
+                if groupImages[messageIdChat] == nil {
+                    containerForwarded.bottomAnchor.constraint(equalTo: imageThumb.topAnchor, constant: -5).isActive = true
+                }
             } else if fileChat != "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"]  as? String ?? "" != "1") {
                 containerForwarded.bottomAnchor.constraint(equalTo: containerViewFile.topAnchor, constant: -5).isActive = true
             } else if containerMessage.subviews.contains(containerLinkMessage) {
@@ -7790,13 +7822,13 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                                     }
                                 }
                             }
+                            DispatchQueue.main.async {
+                                activityIndicator.stopAnimating()
+                                self.tableChatView.reloadRows(at: [sender.indexPath], with: .none)
+                            }
                         } catch {
                             
                         }
-                        DispatchQueue.main.async {
-                            activityIndicator.stopAnimating()
-                            self.tableChatView.reloadRows(at: [sender.indexPath], with: .none)
-                        }
                     }
                 }
             }
@@ -7853,6 +7885,10 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                         
                     }
                 } else {
+                    if downloadList[sender.video_id] != nil && downloadList[sender.video_id] == sender.indexPath {
+                        return
+                    }
+                    downloadList[sender.video_id] = sender.indexPath
                     for view in sender.imageView.subviews {
                         if view is UIImageView {
                             view.removeFromSuperview()
@@ -7870,7 +7906,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                     trackShape.path = circlePath.cgPath
                     trackShape.fillColor = UIColor.clear.cgColor
                     trackShape.lineWidth = 10
-                    trackShape.strokeColor = UIColor.blueBubbleColor.withAlphaComponent(0.3).cgColor
+                    trackShape.strokeColor = UIColor.mentionColor.withAlphaComponent(0.3).cgColor
                     container.backgroundColor = .clear
                     container.layer.addSublayer(trackShape)
                     let shapeLoading = CAShapeLayer()
@@ -7878,7 +7914,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                     shapeLoading.fillColor = UIColor.clear.cgColor
                     shapeLoading.lineWidth = 10
                     shapeLoading.strokeEnd = 0
-                    shapeLoading.strokeColor = UIColor.blueBubbleColor.cgColor
+                    shapeLoading.strokeColor = UIColor.mentionColor.cgColor
                     container.layer.addSublayer(shapeLoading)
                     let imageDownload = UIImageView(image: UIImage(systemName: "arrow.down", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
                     imageDownload.tintColor = .white
@@ -7888,7 +7924,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                     imageDownload.centerYAnchor.constraint(equalTo: sender.imageView.centerYAnchor).isActive = true
                     imageDownload.widthAnchor.constraint(equalToConstant: 30).isActive = true
                     imageDownload.heightAnchor.constraint(equalToConstant: 30).isActive = true
-                    Download().startHTTP(forKey: sender.video_id, isImage: false) { (name, progress) in
+                    Download().startHTTP(forKey: sender.video_id) { (name, progress) in
                         DispatchQueue.main.async {
                             guard progress == 100 else {
                                 shapeLoading.strokeEnd = CGFloat(progress / 100)
@@ -7958,7 +7994,11 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                     catch {
                         
                     }
-                }   else {
+                } 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()
@@ -7977,14 +8017,14 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                     trackShape.path = circlePath.cgPath
                     trackShape.fillColor = UIColor.clear.cgColor
                     trackShape.lineWidth = 5
-                    trackShape.strokeColor = UIColor.blueBubbleColor.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.blueBubbleColor.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
@@ -7993,7 +8033,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPla
                     imageupload.centerYAnchor.constraint(equalTo: containerLoading.centerYAnchor).isActive = true
                     imageupload.centerXAnchor.constraint(equalTo: containerLoading.centerXAnchor).isActive = true
                     
-                    Download().startHTTP(forKey: sender.file_id, isImage: false) { (name, progress) in
+                    Download().startHTTP(forKey: sender.file_id) { (name, progress) in
                         DispatchQueue.main.async {
                             guard progress == 100 else {
                                 shapeLoading.strokeEnd = CGFloat(progress / 100)

+ 41 - 19
NexilisLite/NexilisLite/Source/View/Chat/EditorStarMessages.swift

@@ -853,8 +853,12 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
         if stringURl.lowercased().starts(with: "www.") {
             stringURl = "https://" + stringURl.replacingOccurrences(of: "www.", with: "")
         }
-        guard let url = URL(string: stringURl) else { return }
-        UIApplication.shared.open(url)
+        if Nexilis.checkingAccess(key: "secure_browser") {
+            APIS.openUrl(url: stringURl)
+        } else {
+            guard let url = URL(string: stringURl) else { return }
+            UIApplication.shared.open(url)
+        }
     }
     
     func getData() {
@@ -1122,7 +1126,7 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
                     imageDownload.centerYAnchor.constraint(equalTo: sender.imageView.centerYAnchor).isActive = true
                     imageDownload.widthAnchor.constraint(equalToConstant: 30).isActive = true
                     imageDownload.heightAnchor.constraint(equalToConstant: 30).isActive = true
-                    Download().startHTTP(forKey: sender.video_id, isImage: false) { (name, progress) in
+                    Download().startHTTP(forKey: sender.video_id) { (name, progress) in
                         DispatchQueue.main.async {
                             guard progress == 100 else {
                                 shapeLoading.strokeEnd = CGFloat(progress / 100)
@@ -1227,7 +1231,7 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
                     imageupload.centerYAnchor.constraint(equalTo: containerLoading.centerYAnchor).isActive = true
                     imageupload.centerXAnchor.constraint(equalTo: containerLoading.centerXAnchor).isActive = true
                     
-                    Download().startHTTP(forKey: sender.file_id, isImage: false) { (name, progress) in
+                    Download().startHTTP(forKey: sender.file_id) { (name, progress) in
                         DispatchQueue.main.async {
                             guard progress == 100 else {
                                 shapeLoading.strokeEnd = CGFloat(progress / 100)
@@ -1582,21 +1586,39 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
         }
     }
     
-    public func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
-            switch interaction {
-            case .invokeDefaultAction:
-                let gesture = ObjectGesture()
-                gesture.message_id = URL.absoluteString
-                tapMessageText(gesture)
-                return false
-            case .presentActions:
-                UIPasteboard.general.string = URL.absoluteString
-                self.view.makeToast("Link Copied".localized(), duration: 3)
-                return false
-            case .preview:
-                return true
-            @unknown default:
-                return true
+    public func textView(_ textView: UITextView, shouldInteractWith URL: URL?, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
+        var urlString: String?
+
+        if let url = URL {
+            urlString = url.absoluteString
+        } else {
+            if let range = Range(characterRange, in: textView.text) {
+                let tappedText = String(textView.text[range])
+                urlString = tappedText
             }
         }
+        
+        guard let finalURL = urlString else {
+            return false
+        }
+
+        switch interaction {
+        case .invokeDefaultAction:
+            let gesture = ObjectGesture()
+            gesture.message_id = finalURL
+            tapMessageText(gesture)
+            return false
+
+        case .presentActions:
+            UIPasteboard.general.string = finalURL
+            self.view.makeToast("Link Copied".localized(), duration: 3)
+            return false
+
+        case .preview:
+            return true
+
+        @unknown default:
+            return true
+        }
+    }
 }

+ 1 - 6
NexilisLite/NexilisLite/Source/View/Chat/ListGroupImages.swift

@@ -673,12 +673,7 @@ class ListGroupImages: UIViewController, UITableViewDataSource, UITableViewDeleg
                     }
                 }
                 if imagePath == nil {
-                    Download().startHTTP(forKey: image) { (name, progress) in
-                        guard progress == 100 else {
-                            return
-                        }
-                    }
-                    imagePath = UIImage(named: "Send-Image", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
+                    return CGSize(width: 100, height: 100)
                 }
             }
             catch {

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

@@ -311,50 +311,60 @@ class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITex
     
     @objc func sendTapped() {
         if (image != nil) || (imageVideoData != nil && imageVideoData![.mediaType] as! String == "public.image") {
-            var originalImageName = ""
-            if (fromCopy) {
-                originalImageName = "\(Date().currentTimeMillis())"
-            } else if (imageVideoData![.imageURL] == nil) {
-                originalImageName = "takeImage_\(Date().currentTimeMillis())"
-            } else {
-                let urlImage = (imageVideoData![.imageURL] as! NSURL).absoluteString
-                originalImageName = (urlImage! as NSString).lastPathComponent
-            }
-            let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
-            let compressedImageName = "Nexilis_image_\(Date().currentTimeMillis())_\(originalImageName)"
-            let thumbName = "THUMB_Nexilis_image_\(Date().currentTimeMillis())_\(originalImageName)"
-            let fileURL = documentsDirectory.appendingPathComponent(compressedImageName)
-            var compressedImage:Data?
-            if (image != nil) {
-                compressedImage = image!.jpegData(compressionQuality:  0.5)
-            } else {
-                compressedImage = (imageVideoData![.originalImage] as! UIImage).jpegData(compressionQuality:  0.5)
-            }
-            if let data = compressedImage,
-               !FileManager.default.fileExists(atPath: fileURL.path) {
-                do {
-                    try data.write(to: fileURL)
-                    //print("file saved")
-                } catch {
-                    //print("error saving file:", error)
+            Nexilis.showLoader()
+            DispatchQueue.global().async { [self] in
+                var originalImageName = ""
+                if (fromCopy) {
+                    originalImageName = "\(Date().currentTimeMillis())"
+                } else if (imageVideoData![.imageURL] == nil) {
+                    originalImageName = "takeImage_\(Date().currentTimeMillis())"
+                } else {
+                    let urlImage = (imageVideoData![.imageURL] as! NSURL).absoluteString
+                    originalImageName = (urlImage! as NSString).lastPathComponent
                 }
-            }
-            let thumbImage = UIImage(data: compressedImage!)
-            let fileURLTHUMB = documentsDirectory.appendingPathComponent(thumbName)
-            if let dataThumb = thumbImage!.jpegData(compressionQuality:  0.25),
-               !FileManager.default.fileExists(atPath: fileURLTHUMB.path) {
-                do {
-                    try dataThumb.write(to: fileURLTHUMB)
-                    //print("thumb saved")
-                } catch {
-                    //print("error saving file:", error)
+                let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
+                let compressedImageName = "Nexilis_image_\(Date().currentTimeMillis())_\(originalImageName.components(separatedBy: ".")[0]).jpeg"
+                let thumbName = "THUMB_Nexilis_image_\(Date().currentTimeMillis())_\(originalImageName.components(separatedBy: ".")[0]).jpeg"
+                let fileURL = documentsDirectory.appendingPathComponent(compressedImageName)
+                var compressedImage:Data!
+                if (image != nil) {
+                    compressedImage = image!.jpeg ?? Data()
+                } else {
+                    compressedImage = (imageVideoData![.originalImage] as! UIImage).jpeg ?? Data()
+                }
+                if let compressed = compressImageLikeWhatsApp(UIImage(data: compressedImage) ?? UIImage()) {
+                    compressedImage = compressed
+                }
+                if let data = compressedImage,
+                   !FileManager.default.fileExists(atPath: fileURL.path) {
+                    do {
+                        try data.write(to: fileURL)
+                        //print("file saved")
+                    } catch {
+                        //print("error saving file:", error)
+                    }
+                }
+                let thumbImage = UIImage(data: compressedImage!)
+                let fileURLTHUMB = documentsDirectory.appendingPathComponent(thumbName)
+                if let dataThumb = thumbImage!.jpegData(compressionQuality:  0.25),
+                   !FileManager.default.fileExists(atPath: fileURLTHUMB.path) {
+                    do {
+                        try dataThumb.write(to: fileURLTHUMB)
+                        //print("thumb saved")
+                    } catch {
+                        //print("error saving file:", error)
+                    }
+                }
+                DispatchQueue.main.async {
+                    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)
+                        } else {
+                            delegate!.sendChatFromPreviewImage(message_text: textFieldSend.text!, attachment_flag: "1", image_id: compressedImageName, video_id: "", thumb_id: thumbName, gif_id: "", viewController: self)
+                        }
+                    }
                 }
-            }
-            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)
-            } else {
-                delegate!.sendChatFromPreviewImage(message_text: textFieldSend.text!, attachment_flag: "1", image_id: compressedImageName, video_id: "", thumb_id: thumbName, gif_id: "", viewController: self)
             }
         } else {
             Nexilis.showLoader()
@@ -420,7 +430,7 @@ class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITex
                         if (fromCopy && dataGIF != nil) {
                             originalVideoName = "\(Date().currentTimeMillis())_copyGif"
                             renamedVideoName = "Nexilis_gif_\(Date().currentTimeMillis())_\(originalVideoName)"
-                            thumbName = "THUMB_Nexilis_gif_\(Date().currentTimeMillis())_\(originalVideoName)"
+                            thumbName = "THUMB_Nexilis_gif_\(Date().currentTimeMillis())_\(originalVideoName.components(separatedBy: ".")[0]).jpeg"
                         } else {
                             if imageVideoData != nil {
                                 urlVideo = (imageVideoData![.mediaURL] as! NSURL).absoluteString!
@@ -429,7 +439,7 @@ class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITex
                             }
                             originalVideoName = (urlVideo as NSString).lastPathComponent
                             renamedVideoName = "Nexilis_video_\(Date().currentTimeMillis())_\(originalVideoName)"
-                            thumbName = "THUMB_Nexilis_video_\(Date().currentTimeMillis())_\(originalVideoName)"
+                            thumbName = "THUMB_Nexilis_video_\(Date().currentTimeMillis())_\(originalVideoName.components(separatedBy: ".")[0]).jpeg"
                         }
                         let fileURL = documentsDirectory.appendingPathComponent(renamedVideoName)
                         if !FileManager.default.fileExists(atPath: fileURL.path) {
@@ -498,6 +508,38 @@ class PreviewAttachmentImageVideo: UIViewController, UIScrollViewDelegate, UITex
         }
     }
     
+    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))
+        }
+    }
+    
     @objc func cancelTapped() {
         self.dismiss(animated: true, completion: nil)
     }

+ 1 - 1
NexilisLite/NexilisLite/Source/View/Control/BackupRestoreView.swift

@@ -251,7 +251,7 @@ public class BackupRestoreView: UIViewController, UITableViewDataSource, UITable
                         
                     }
                 } else {
-                    Download().startHTTP(forKey: getFileName(option: optionBackup, fileId: fileIdBackup), isImage: false) { (name, progress) in
+                    Download().startHTTP(forKey: getFileName(option: optionBackup, fileId: fileIdBackup)) { (name, progress) in
                         DispatchQueue.main.async { [self] in
                             guard progress == 100 else {
                                 labelRestoring.text = "Downloading...".localized() + "  \(progress)%"

+ 6 - 12
NexilisLite/NexilisLite/Source/View/Control/ContactChatViewController.swift

@@ -48,7 +48,7 @@ class ContactChatViewController: UITableViewController {
     var noData = false
     
     var loadingData = true
-    var waitingLoading = false
+    var timerReloadData = Timer()
     
     var noUCList = false
     
@@ -265,17 +265,11 @@ class ContactChatViewController: UITableViewController {
 //    }
     
     private func reloadAllData() {
-        DispatchQueue.global().async { [self] in
-            if waitingLoading {
-                return
-            }
-            waitingLoading = true
-            while loadingData {
-                Thread.sleep(forTimeInterval: 0.5)
-            }
-            waitingLoading = false
-            getData()
-        }
+        timerReloadData.invalidate()
+        timerReloadData = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: {_ in
+            self.getData()
+        })
+        timerReloadData.fire()
     }
     
     @objc func onReloadTab(notification: NSNotification) {

+ 4 - 3
NexilisLite/NexilisLite/Source/View/Control/ProfileViewController.swift

@@ -664,11 +664,12 @@ public class ProfileViewController: UITableViewController, UITextFieldDelegate {
                 }
             }
         } else {
+            let imageUser = self.user != nil ? self.user!.thumb : self.picture
             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(self.user!.thumb)
+                let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(imageUser)
                 if FileManager.default.fileExists(atPath: imageURL.path) {
                     let image    = UIImage(contentsOfFile: imageURL.path)
                     let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
@@ -677,9 +678,9 @@ public class ProfileViewController: UITableViewController, UITextFieldDelegate {
                     previewImageVC.modalPresentationStyle = .custom
                     previewImageVC.modalTransitionStyle  = .crossDissolve
                     self.present(previewImageVC, animated: true, completion: nil)
-                } else if FileEncryption.shared.isSecureExists(filename: self.user!.thumb) {
+                } else if FileEncryption.shared.isSecureExists(filename: imageUser) {
                     do {
-                        if var data = try FileEncryption.shared.readSecure(filename: self.user!.thumb) {
+                        if var data = try FileEncryption.shared.readSecure(filename: imageUser) {
                             let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
                             if dataDecrypt != nil {
                                 data = dataDecrypt!

+ 3 - 2
NexilisLite/NexilisLite/Source/View/Streaming/QmeraCreateStreamingViewController.swift

@@ -325,8 +325,9 @@ public class QmeraCreateStreamingViewController: UITableViewController {
                 guard let json = String(data: try! JSONSerialization.data(withJSONObject: data, options: []), encoding: String.Encoding.utf8) else {
                     return
                 }
-                
-                if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.createLS(title: "0~\(data["title"] ?? "")", type: data["type"] as! String, category: "3", tagline: data["tagline"] as! String, notifType: data["broadcast_type"] as! String, blogId: data["blog"] as! String, data: json)) {
+                let title = data["title"] as? String ?? ""
+                let tagline = data["tagline"] as? String ?? ""
+                if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.createLS(title: "0~\(title.toStupidString())", type: data["type"] as! String, category: "3", tagline: tagline.toStupidString(), notifType: data["broadcast_type"] as! String, blogId: data["blog"] as! String, data: json)) {
                     if response.getBody(key: CoreMessage_TMessageKey.ERRCOD) != "00" {
                         let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
                         imageView.tintColor = .white