Explorar o código

update editor mention, paste and notif offline

alqindiirsyam hai 5 meses
pai
achega
22eb0d691e

+ 6 - 6
NexilisLite/NexilisLite/Source/Callback.swift

@@ -159,12 +159,12 @@ class NetworkMonitor {
                 OutgoingThread.default.set(wait: !connected)
                 if !connected {
                     fromDisconnect = true
-//                    DispatchQueue.main.async {
-//                        let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
-//                        imageView.tintColor = .white
-//                        let banner = FloatingNotificationBanner(title: "Check your connection".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
-//                        banner.show()
-//                    }
+                    DispatchQueue.main.async {
+                        let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
+                        imageView.tintColor = .white
+                        let banner = FloatingNotificationBanner(title: "Check your connection".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
+                        banner.show()
+                    }
                 }
                 if connected && fromDisconnect {
                     fromDisconnect = false

+ 15 - 0
NexilisLite/NexilisLite/Source/View/Chat/CustomTextView.swift

@@ -7,6 +7,10 @@
 
 import UIKit
 
+protocol CustomTextViewPasteDelegate : AnyObject {
+    func customTextViewDidPasteText(image: UIImage?, dataGIF: Data?)
+}
+
 class CustomTextView: UITextView {
     
     override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
@@ -71,5 +75,16 @@ class CustomTextView: UITextView {
         return char == " " || char == "\n"
     }
     
+    weak var customDelegate: CustomTextViewPasteDelegate?
+    override func paste(_ sender: Any?) {
+        if let pasteboardItems = UIPasteboard.general.items.first {
+            if pasteboardItems["public.jpeg"] != nil || pasteboardItems["public.png"] != nil || pasteboardItems["public.gif"] != nil || (pasteboardItems.keys.first != nil && pasteboardItems.keys.first!.contains(".gif")) {
+                let dataGif = UIPasteboard.general.data(forPasteboardType: "com.compuserve.gif")
+                customDelegate?.customTextViewDidPasteText(image: pasteboardItems["public.png"] as? UIImage ?? pasteboardItems["public.jpeg"] as? UIImage, dataGIF: dataGif)
+                return
+            }
+        }
+        super.paste(sender)
+    }
 
 }

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

@@ -24,7 +24,7 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
     @IBOutlet var buttonSendPhoto: UIButton!
     @IBOutlet var buttonSendSticker: UIButton!
     @IBOutlet var buttonSendFile: UIButton!
-    @IBOutlet var textFieldSend: UITextView!
+    @IBOutlet var textFieldSend: CustomTextView!
     @IBOutlet var heightTextFieldSend: NSLayoutConstraint!
     @IBOutlet var buttonSendChat: UIButton!
     @IBOutlet var tableChatView: UITableView!
@@ -103,7 +103,7 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
     var timerCredential: [String: Timer] = [:]
     var audioPlayer: AVAudioPlayer?
     var editVC = UIViewController()
-    var editTextView = UITextView()
+    var editTextView = CustomTextView()
     var isEditingMessage = false
     var constraintBottomeditTextView: NSLayoutConstraint!
     var constraintHeighteditTextView: NSLayoutConstraint!
@@ -131,7 +131,11 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
         if (self.dataTopic["chat_id"]  as? String ?? "" != "") {
             l_pin = self.dataTopic["chat_id"]  as? String ?? ""
         }
-        SecureUserDefaults.shared.set("\(textFieldSend.textColor != UIColor.lightGray ? textFieldSend.text! : ""),\(reffId ?? "")", forKey: "saved_\(l_pin)")
+        let data: [String: String] = ["text": self.textFieldSend.textColor != UIColor.lightGray ? self.textFieldSend.text! : "", "reffId": self.reffId ?? ""]
+        if let jsonData = try? JSONSerialization.data(withJSONObject: data, options: []),
+           let jsonString = String(data: jsonData, encoding: .utf8) {
+            SecureUserDefaults.shared.set(jsonString, forKey: "new_saved_\(l_pin)")
+        }
     }
     
     public override func viewDidAppear(_ animated: Bool) {
@@ -199,6 +203,7 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
         textFieldSend.layer.borderColor = UIColor.lightGray.withAlphaComponent(0.5).cgColor
         textFieldSend.font = UIFont.systemFont(ofSize: 12)
         textFieldSend.delegate = self
+        textFieldSend.customDelegate = self
         textFieldSend.allowsEditingTextAttributes = true
         
         navigationItem.rightBarButtonItem?.tintColor = UIColor.secondaryColor
@@ -306,7 +311,7 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
                             }
                             _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE", _where: "(l_pin='\(self.dataGroup["group_id"]!!)' and chat_id='\(self.dataTopic["chat_id"]!!)') and message_scope_id='4'")
                             _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE_SUMMARY", _where: "l_pin='\(l_pin)'")
-                            SecureUserDefaults.shared.removeValue(forKey: "saved_\(l_pin)")
+                            SecureUserDefaults.shared.removeValue(forKey: "new_saved_\(l_pin)")
                             NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
                             if self.fromNotification {
                                 self.didTapExit()
@@ -476,16 +481,20 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
             if (self.dataTopic["chat_id"]  as? String ?? "" != "") {
                 l_pin = self.dataTopic["chat_id"]  as? String ?? ""
             }
-            if let dataSaved: String = SecureUserDefaults.shared.value(forKey: "saved_\(l_pin)") {
-                let last_m = dataSaved.components(separatedBy: ",")[0]
-                let last_r = dataSaved.components(separatedBy: ",")[1]
-                if !last_m.isEmpty {
-                    textFieldSend.text = last_m
-                    textFieldSend.textColor = UIColor.black
-                }
-                
-                if !last_r.isEmpty {
-                    handleReply(indexPath: IndexPath(row: 0, section: 0), reffId: last_r)
+            if let dataSaved: String = SecureUserDefaults.shared.value(forKey: "new_saved_\(l_pin)") {
+                let data = dataSaved
+                if let jsonData = data.data(using: .utf8),
+                   let dataJson = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: String] {
+                    let last_m = dataJson["text"] ?? ""
+                    let last_r = dataJson["reffId"] ?? ""
+                    if !last_m.isEmpty {
+                        textFieldSend.attributedText = last_m.richText(isEditing: true, group_id: self.dataGroup["group_id"]  as? String ?? "", listMentionInTextField: listMentionInTextField)
+                        textFieldSend.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : UIColor.black
+                    }
+                    
+                    if !last_r.isEmpty {
+                        handleReply(indexPath: IndexPath(row: 0, section: 0), reffId: last_r)
+                    }
                 }
             }
             tableChatView.scrollToBottom(isAnimated: false)
@@ -2418,63 +2427,95 @@ extension EditorGroup: UIDocumentPickerDelegate, DocumentPickerDelegate, QLPrevi
     }
 }
 
-extension EditorGroup: UITextViewDelegate {
+extension EditorGroup: UITextViewDelegate, CustomTextViewPasteDelegate {
+    func customTextViewDidPasteText(image: UIImage?, dataGIF: Data?) {
+        let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
+        previewImageVC.image = image
+        previewImageVC.isGIF = image == nil
+        previewImageVC.fromCopy = true
+        previewImageVC.dataGIF = dataGIF
+        previewImageVC.currentTextTextField = textFieldSend.text
+        previewImageVC.modalPresentationStyle = .custom
+        previewImageVC.delegate = self
+        previewImageVC.isAck = self.isAck
+        previewImageVC.isConfidential = self.isConfidential
+        self.present(previewImageVC, animated: true, completion: nil)
+    }
+    
     public func textViewDidChangeSelection(_ textView: UITextView) {
         lastPositionCursorMention = textView.selectedRange.location
-        let fulltextForMention = textView.text.substring(from: 0, to: lastPositionCursorMention - 1)
+        guard lastPositionCursorMention > 0 else {
+            hideMention()
+            return
+        }
+
+        let fulltextForMention = textView.text.prefix(lastPositionCursorMention)
         var isShowMention = false
-        if lastPositionCursorMention > 0 {
-            var listHaveToRemoved: [User] = []
-            var continueCheckMention = true
-            if listMentionInTextField.count > 0 {
-                for i in 0..<listMentionInTextField.count {
-                    if listMentionInTextField[i].ex_block != nil && !listMentionInTextField[i].ex_block!.isEmpty {
-                        let nameWithMention = ("@" + listMentionInTextField[i].firstName + " " + listMentionInTextField[i].lastName).trimmingCharacters(in: .whitespaces)
-                        var rangeLower = Int(listMentionInTextField[i].ex_block!)! - nameWithMention.count
-                        var rangeUpper = Int(listMentionInTextField[i].ex_block!)!
-                        if textView.text.substring(from: rangeLower, to: rangeUpper - 1) == nameWithMention {
-                            if lastPositionCursorMention >= rangeLower + 1 && lastPositionCursorMention <= rangeUpper {
-                                continueCheckMention = false
-                            }
-                        } else {
-                            if listMentionInTextField[i].ex_offmp!.isEmpty {
-                                rangeLower = rangeLower + listMentionWithText.count
-                                rangeUpper = rangeUpper + listMentionWithText.count
-                            } else {
-                                rangeLower = rangeLower + (listMentionWithText.count - Int(listMentionInTextField[i].ex_offmp!)!)
-                                rangeUpper = rangeUpper + (listMentionWithText.count - Int(listMentionInTextField[i].ex_offmp!)!)
-                            }
-                            if textView.text.substring(from: rangeLower, to: rangeUpper - 1) == nameWithMention {
-                                if lastPositionCursorMention >= rangeLower + 1 && lastPositionCursorMention <= rangeUpper {
-                                    continueCheckMention = false
-                                }
-                                listMentionInTextField[i].ex_block! = "\(rangeUpper)"
-                                listMentionInTextField[i].ex_offmp! = "\(textView.text.count)"
-                            } else {
-                                listHaveToRemoved.append(listMentionInTextField[i])
-                            }
-                        }
+        var listHaveToRemoved: [User] = []
+        var continueCheckMention = true
+
+        for mention in listMentionInTextField where mention.ex_block?.isEmpty == false {
+            let nameWithMention = "@\(mention.firstName) \(mention.lastName)".trimmingCharacters(in: .whitespaces)
+            guard let blockPosition = Int(mention.ex_block!) else { continue }
+            
+            let lowerBound = textView.text.index(textView.text.startIndex, offsetBy: blockPosition - nameWithMention.count)
+            let upperBound = textView.text.index(textView.text.startIndex, offsetBy: blockPosition)
+            let range = lowerBound..<upperBound
+            
+            if textView.text[range] == nameWithMention {
+                if lastPositionCursorMention >= textView.text.distance(from: textView.text.startIndex, to: lowerBound) + 1,
+                   lastPositionCursorMention <= textView.text.distance(from: textView.text.startIndex, to: upperBound) {
+                    continueCheckMention = false
+                    break
+                }
+            } else {
+                let offset = mention.ex_offmp?.isEmpty == false ? listMentionWithText.count - Int(mention.ex_offmp!)! : listMentionWithText.count
+                let adjustedLowerBound = textView.text.index(lowerBound, offsetBy: offset)
+                let adjustedUpperBound = textView.text.index(upperBound, offsetBy: offset)
+                let adjustedRange = adjustedLowerBound..<adjustedUpperBound
+                
+                if textView.text[adjustedRange] == nameWithMention {
+                    if lastPositionCursorMention >= textView.text.distance(from: textView.text.startIndex, to: adjustedLowerBound) + 1,
+                       lastPositionCursorMention <= textView.text.distance(from: textView.text.startIndex, to: adjustedUpperBound) {
+                        continueCheckMention = false
+                        break
                     }
+                    mention.ex_block = "\(textView.text.distance(from: textView.text.startIndex, to: adjustedUpperBound))"
+                    mention.ex_offmp = "\(textView.text.count)"
+                } else {
+                    listHaveToRemoved.append(mention)
                 }
             }
-//            listMentionInTextField.removeAll(where: { listHaveToRemoved.contains($0) })
-            if continueCheckMention {
-                let splitBreak = fulltextForMention.components(separatedBy: "\n")
-                let indexLastBreak = splitBreak.lastIndex(where: { $0.contains("@") })
-                if indexLastBreak != nil {
-                    let splitSpace = splitBreak[indexLastBreak!].components(separatedBy: " ")
-                    let indexLastMention = splitSpace.lastIndex(where: { $0.substring(from: 0, to: 0) == "@" })
-                    if indexLastMention != nil && fulltextForMention.substring(from: lastPositionCursorMention - 1, to: lastPositionCursorMention - 1) != " " && fulltextForMention.substring(from: lastPositionCursorMention - 1, to: lastPositionCursorMention - 1) != "\n" {
-                        let fullTextMention = splitSpace[indexLastMention!]
-                        showMention(text: fullTextMention.substring(from: 1, to: fullTextMention.count))
-                        isShowMention = true
+        }
+
+        if continueCheckMention {
+            if let indexLastBreak = fulltextForMention.split(separator: "\n").lastIndex(where: { $0.contains("@") }) {
+                let splitSpace = fulltextForMention.split(separator: "\n")[indexLastBreak].split(separator: " ")
+                if let indexLastMention = splitSpace.lastIndex(where: { $0.hasPrefix("@") }),
+                   let lastChar = fulltextForMention.last,
+                   lastChar != " " && lastChar != "\n" {
+                    showMention(text: String(splitSpace[indexLastMention].dropFirst()))
+                    isShowMention = true
+                }
+            } else if fulltextForMention.count >= 3 && self.textFieldSend.textColor != UIColor.lightGray {
+                if let indexLastBreak = fulltextForMention.split(separator: "\n").lastIndex(where: { $0.count >= 3 }) {
+                    let splitSpace = fulltextForMention.split(separator: "\n")[indexLastBreak].split(separator: " ")
+                    if let indexLastMention = splitSpace.lastIndex(of: splitSpace.last ?? ""),
+                       let lastChar = fulltextForMention.last,
+                       lastChar != " " && lastChar != "\n" {
+                        if String(splitSpace[indexLastMention]).count >= 3 {
+                            showMention(text: String(splitSpace[indexLastMention]))
+                            isShowMention = true
+                        }
                     }
                 }
             }
         }
+
         if !isShowMention {
             hideMention()
         }
+
         var nowTextFieldSend = self.textFieldSend
         if isEditingMessage {
             nowTextFieldSend = editTextView
@@ -2608,6 +2649,8 @@ extension EditorGroup: UITextViewDelegate {
     
     private func hideMention() {
         if self.contraintBottomMention.constant > 0 {
+            listMentionWithText.removeAll()
+            tableMention.reloadData()
             self.contraintBottomMention.constant = 0 - self.heightTableMention.constant
             UIView.animate(withDuration: 0.5, animations: {
                 self.view.layoutIfNeeded()
@@ -2810,29 +2853,12 @@ extension EditorGroup: UITextViewDelegate {
     
     public func textViewDidEndEditing(_ textView: UITextView) {
         if textView.text.isEmpty && textView != editTextView {
-            textView.text = "Send message".localized()
             textView.textColor = UIColor.lightGray
+            textView.text = "Send message".localized()
         }
     }
     
     public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
-        if let pasteboardItems = UIPasteboard.general.items.first {
-            if pasteboardItems["public.jpeg"] != nil || pasteboardItems["public.png"] != nil || pasteboardItems["public.gif"] != nil || (pasteboardItems.keys.first != nil && pasteboardItems.keys.first!.contains(".gif")) {
-                let dataGif = UIPasteboard.general.data(forPasteboardType: "com.compuserve.gif")
-                let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
-                previewImageVC.image = pasteboardItems["public.png"] as? UIImage ?? pasteboardItems["public.jpeg"] as? UIImage
-                previewImageVC.isGIF = (pasteboardItems["public.png"] == nil && pasteboardItems["public.jpeg"] == nil)
-                previewImageVC.fromCopy = true
-                previewImageVC.dataGIF = dataGif
-                previewImageVC.currentTextTextField = textFieldSend.text
-                previewImageVC.modalPresentationStyle = .custom
-                previewImageVC.delegate = self
-                previewImageVC.isAck = self.isAck
-                previewImageVC.isConfidential = self.isConfidential
-                self.present(previewImageVC, animated: true, completion: nil)
-                return false
-            }
-        }
         if text.isEmpty {
             if listMentionInTextField.count > 0 {
                 for i in 0..<listMentionInTextField.count {
@@ -3282,7 +3308,7 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
             tapGesture.cancelsTouchesInView = false
             view.addGestureRecognizer(tapGesture)
             
-            editTextView = UITextView()
+            editTextView = CustomTextView()
             editTextView.layer.cornerRadius = textFieldSend.maxCornerRadius()
             editTextView.layer.borderWidth = 1.0
             editTextView.textColor = UIColor.black
@@ -3603,7 +3629,8 @@ 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 ?? "")
-                text = text + "\n\n*[\(formatterDate.string(from: date as Date)) \(formatterTime.string(from: date as Date))] \(dataProfile["name"]!):*\n\(dataMessages[i]["message_text"]  as? String ?? "")"
+                let textCopied = (dataMessages[i]["message_text"]  as? String ?? "").richText(isEditing: true, group_id: self.dataGroup["group_id"]  as? String ?? "", listMentionInTextField: listMentionInTextField)
+                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()
             DispatchQueue.main.async {
@@ -4048,7 +4075,17 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
             tableView.deselectRow(at: indexPath, animated: true)
             let fulltextForMention = textFieldSend.text.substring(from: 0, to: lastPositionCursorMention - 1)
             let diff = textFieldSend.text.count - fulltextForMention.count
-            if let indexLastMention = fulltextForMention.lastIndex(of: "@") {
+            var indexLastMention = fulltextForMention.lastIndex(of: "@")
+            if indexLastMention == nil {
+                if let spaceIndex = fulltextForMention.lastIndex(of: " ") {
+                    indexLastMention = fulltextForMention.index(after: spaceIndex)
+                } else if let breakIndex = fulltextForMention.lastIndex(of: "\n") {
+                    indexLastMention = fulltextForMention.index(after: breakIndex)
+                } else {
+                    indexLastMention = fulltextForMention.firstIndex(of: fulltextForMention.first!)
+                }
+            }
+            if let indexLastMention = indexLastMention {
                 listMentionInTextField.append(listMentionWithText[indexPath.row])
                 let indexIntMention = fulltextForMention.distance(from: fulltextForMention.startIndex, to: indexLastMention)
                 let rangeReplacement = NSRange(location: indexIntMention, length: lastPositionCursorMention - indexIntMention)
@@ -6096,6 +6133,10 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
             dataMessages = self.dataMessages.filter({ $0["message_id"]  as? String ?? "" == reffId })
             self.reffId = reffId
         }
+        if dataMessages.count == 0  {
+            self.deleteReplyView()
+            return
+        }
         UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
             self.constraintTopTextField.constant = self.constraintTopTextField.constant + 50
             if self.contraintBottomMention.constant > 0 {

+ 46 - 36
NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift

@@ -24,7 +24,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
     @IBOutlet var buttonSendPhoto: UIButton!
     @IBOutlet var buttonSendSticker: UIButton!
     @IBOutlet var buttonSendFile: UIButton!
-    @IBOutlet var textFieldSend: UITextView!
+    @IBOutlet var textFieldSend: CustomTextView!
     @IBOutlet var heightTextFieldSend: NSLayoutConstraint!
     @IBOutlet var buttonSendChat: UIButton!
     @IBOutlet var tableChatView: UITableView!
@@ -114,7 +114,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
     var lastY: CGFloat = 0
     var audioPlayer: AVAudioPlayer?
     var editVC = UIViewController()
-    var editTextView = UITextView()
+    var editTextView = CustomTextView()
     var isEditingMessage = false
     var constraintBottomeditTextView: NSLayoutConstraint!
     var constraintHeighteditTextView: NSLayoutConstraint!
@@ -203,6 +203,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
         textFieldSend.layer.borderColor = UIColor.lightGray.withAlphaComponent(0.5).cgColor
         textFieldSend.font = UIFont.systemFont(ofSize: 12)
         textFieldSend.delegate = self
+        textFieldSend.customDelegate = self
         textFieldSend.allowsEditingTextAttributes = true
         
         navigationItem.rightBarButtonItem?.tintColor = UIColor.secondaryColor
@@ -346,7 +347,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                             _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE", _where: "(f_pin='\(self.dataPerson["f_pin"]!!)' or l_pin='\(self.dataPerson["f_pin"]!!)') and (message_scope_id='3' or message_scope_id='18') and is_call_center = 0")
                             _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE_SUMMARY", _where: "l_pin='\(self.dataPerson["f_pin"]!!)'")
                             let l_pin = self.dataPerson["f_pin"]!!
-                            SecureUserDefaults.shared.removeValue(forKey: "saved_\(l_pin)")
+                            SecureUserDefaults.shared.removeValue(forKey: "new_saved_\(l_pin)")
                             NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
                             if self.fromNotification {
                                 self.didTapExit()
@@ -611,16 +612,20 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                 }
             } else {
                 let l_pin = self.dataPerson["f_pin"] as? String ?? ""
-                if let dataSaved: String = SecureUserDefaults.shared.value(forKey: "saved_\(l_pin)") {
-                    let last_m = dataSaved.components(separatedBy: ",")[0]
-                    let last_r = dataSaved.components(separatedBy: ",")[1]
-                    if !last_m.isEmpty {
-                        textFieldSend.text = last_m
-                        textFieldSend.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : UIColor.black
-                    }
-                    
-                    if !last_r.isEmpty {
-                        handleReply(indexPath: IndexPath(row: 0, section: 0), reffId: last_r)
+                if let dataSaved: String = SecureUserDefaults.shared.value(forKey: "new_saved_\(l_pin)") {
+                    let data = dataSaved
+                    if let jsonData = data.data(using: .utf8),
+                       let dataJson = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: String] {
+                        let last_m = dataJson["text"] ?? ""
+                        let last_r = dataJson["reffId"] ?? ""
+                        if !last_m.isEmpty {
+                            textFieldSend.attributedText = last_m.richText(isEditing: true)
+                            textFieldSend.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : UIColor.black
+                        }
+                        
+                        if !last_r.isEmpty {
+                            handleReply(indexPath: IndexPath(row: 0, section: 0), reffId: last_r)
+                        }
                     }
                 }
                 tableChatView.scrollToBottom(isAnimated: false)
@@ -2423,7 +2428,11 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
         self.removeFromParent()
         if !self.isContactCenter {
             let l_pin = self.dataPerson["f_pin"]!!
-            SecureUserDefaults.shared.set("\(self.textFieldSend.textColor != UIColor.lightGray ? self.textFieldSend.text! : ""),\(self.reffId ?? "")", forKey: "saved_\(l_pin)")
+            let data: [String: String] = ["text": self.textFieldSend.textColor != UIColor.lightGray ? self.textFieldSend.text! : "", "reffId": self.reffId ?? ""]
+            if let jsonData = try? JSONSerialization.data(withJSONObject: data, options: []),
+               let jsonString = String(data: jsonData, encoding: .utf8) {
+                SecureUserDefaults.shared.set(jsonString, forKey: "new_saved_\(l_pin)")
+            }
         }
     }
     
@@ -3638,7 +3647,21 @@ extension EditorPersonal: UIDocumentPickerDelegate, DocumentPickerDelegate, QLPr
 }
 
 //ETV
-extension EditorPersonal: UITextViewDelegate {
+extension EditorPersonal: UITextViewDelegate, CustomTextViewPasteDelegate {
+    func customTextViewDidPasteText(image: UIImage?, dataGIF: Data?) {
+        let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
+        previewImageVC.image = image
+        previewImageVC.isGIF = image == nil
+        previewImageVC.fromCopy = true
+        previewImageVC.dataGIF = dataGIF
+        previewImageVC.currentTextTextField = textFieldSend.text
+        previewImageVC.modalPresentationStyle = .custom
+        previewImageVC.delegate = self
+        previewImageVC.isAck = self.isAck
+        previewImageVC.isConfidential = self.isConfidential
+        self.present(previewImageVC, animated: true, completion: nil)
+    }
+    
     public func textViewDidChangeSelection(_ textView: UITextView) {
         var nowTextFieldSend = self.textFieldSend
         if isEditingMessage {
@@ -3905,24 +3928,6 @@ extension EditorPersonal: UITextViewDelegate {
     }
     
     public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
-        if let pasteboardItems = UIPasteboard.general.items.first {
-            if pasteboardItems["public.jpeg"] != nil || pasteboardItems["public.png"] != nil || pasteboardItems["public.gif"] != nil || (pasteboardItems.keys.first != nil && pasteboardItems.keys.first!.contains(".gif")) {
-                let dataGif = UIPasteboard.general.data(forPasteboardType: "com.compuserve.gif")
-                let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
-                previewImageVC.image = pasteboardItems["public.png"] as? UIImage ?? pasteboardItems["public.jpeg"] as? UIImage
-                previewImageVC.isGIF = (pasteboardItems["public.png"] == nil && pasteboardItems["public.jpeg"] == nil)
-                previewImageVC.fromCopy = true
-                previewImageVC.dataGIF = dataGif
-                previewImageVC.currentTextTextField = textFieldSend.text
-                previewImageVC.modalPresentationStyle = .custom
-                previewImageVC.delegate = self
-                previewImageVC.isAck = self.isAck
-                previewImageVC.isConfidential = self.isConfidential
-                previewImageVC.isCC = self.isContactCenter
-                self.present(previewImageVC, animated: true, completion: nil)
-                return false
-            }
-        }
         if (self.textFieldSend.text.count == 0) {
             return text != "\n"
         }
@@ -4360,7 +4365,7 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
             tapGesture.cancelsTouchesInView = false
             view.addGestureRecognizer(tapGesture)
             
-            editTextView = UITextView()
+            editTextView = CustomTextView()
             editTextView.layer.cornerRadius = textFieldSend.maxCornerRadius()
             editTextView.layer.borderWidth = 1.0
             editTextView.textColor = UIColor.black
@@ -4681,10 +4686,11 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
                 formatterTime.dateFormat = "HH:mm"
                 formatterTime.locale = NSLocale(localeIdentifier: "id") as Locale?
                 let dataProfile = getDataProfile(message_id: dataMessages[i]["message_id"]  as? String ?? "")
+                let textCopied = (dataMessages[i]["message_text"]  as? String ?? "").richText(isEditing: true)
                 if text.isEmpty {
-                    text = "*[\(formatterDate.string(from: date as Date)) \(formatterTime.string(from: date as Date))] \(dataProfile["name"]!):*\n\(dataMessages[i]["message_text"]  as? String ?? "")"
+                    text = "*[\(formatterDate.string(from: date as Date)) \(formatterTime.string(from: date as Date))] \(dataProfile["name"]!):*\n\(textCopied.string)"
                 } else {
-                    text = text + "\n\n*[\(formatterDate.string(from: date as Date)) \(formatterTime.string(from: date as Date))] \(dataProfile["name"]!):*\n\(dataMessages[i]["message_text"]  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()
@@ -7439,6 +7445,10 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
             dataMessages = self.dataMessages.filter({ $0["message_id"]  as? String ?? "" == reffId })
             self.reffId = reffId
         }
+        if dataMessages.count == 0  {
+            self.deleteReplyView()
+            return
+        }
         UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
             self.constraintTopTextField.constant = self.constraintTopTextField.constant + 50
         }, completion: nil)