|
@@ -80,7 +80,15 @@ public class EditorGroup: UIViewController {
|
|
|
var buttonDown: UIButton!
|
|
|
var keyboardHeightForMention: CGFloat?
|
|
|
var listMentionWithText:[User] = []
|
|
|
+ var listMentionInTextField:[User] = []
|
|
|
var showingLink = ""
|
|
|
+ var isAlwaysHideLinkPreview = false
|
|
|
+ var timerCheckLink: Timer?
|
|
|
+ var lastPositionCursorMention = 0
|
|
|
+ var timerLongPressLink: Timer?
|
|
|
+ var lastTouchPoint: CGPoint = .zero
|
|
|
+ var isLinkCopied = false
|
|
|
+ var touchedSubview = UIView()
|
|
|
|
|
|
public override func viewDidDisappear(_ animated: Bool) {
|
|
|
if self.isMovingFromParent {
|
|
@@ -1211,7 +1219,7 @@ public class EditorGroup: UIViewController {
|
|
|
if (reffId != nil) {
|
|
|
reff_id = reffId!
|
|
|
}
|
|
|
- let message_text = message_text.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
+ var message_text = message_text.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
let idMe = UserDefaults.standard.string(forKey: "me") as String?
|
|
|
var opposite_pin = self.dataGroup["group_id"] as! String
|
|
|
if (self.dataTopic["chat_id"] as! String != "") {
|
|
@@ -1221,6 +1229,19 @@ public class EditorGroup: UIViewController {
|
|
|
if isAck {
|
|
|
read_receipts = "8"
|
|
|
}
|
|
|
+ if message_text.contains("@") && listMentionInTextField.count > 0 {
|
|
|
+ for i in 0..<listMentionInTextField.count {
|
|
|
+ let nameWithMention = ("@" + listMentionInTextField[i].firstName + " " + listMentionInTextField[i].lastName).trimmingCharacters(in: .whitespaces)
|
|
|
+ let upperBound = Int(listMentionInTextField[i].ex_block!)! - 1
|
|
|
+ let lowerBound = Int(listMentionInTextField[i].ex_block!)! - nameWithMention.count
|
|
|
+ var afterMention = ""
|
|
|
+ if upperBound + 1 != message_text.count && message_text.substring(from: upperBound + 1, to: upperBound + 1) != "\n" && message_text.substring(from: upperBound + 1, to: upperBound + 1) != " " && message_text.substring(from: upperBound + 1, to: upperBound + 1) != "" {
|
|
|
+ afterMention = " "
|
|
|
+ }
|
|
|
+ let stringRange = message_text.index(message_text.startIndex, offsetBy: lowerBound)..<message_text.index(message_text.startIndex, offsetBy: upperBound + 1)
|
|
|
+ message_text = message_text.replacingOccurrences(of: nameWithMention, with: "@\(listMentionInTextField[i].pin)" + afterMention, range: stringRange)
|
|
|
+ }
|
|
|
+ }
|
|
|
let message = CoreMessage_TMessageBank.sendMessage(l_pin: dataGroup["group_id"] as! String, message_scope_id: message_scope_id, status: status, message_text: message_text, credential: credential, attachment_flag: attachment_flag, ex_blog_id: ex_blog_id, message_large_text: message_large_text, ex_format: ex_format, image_id: image_id, audio_id: audio_id, video_id: video_id, file_id: file_id, thumb_id: thumb_id, reff_id: reff_id, read_receipts: read_receipts, chat_id: dataTopic["chat_id"] as! String, is_call_center: is_call_center, call_center_id: call_center_id, opposite_pin: opposite_pin)
|
|
|
Nexilis.addQueueMessage(message: message)
|
|
|
let messageId = String(message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID]!)
|
|
@@ -1261,6 +1282,7 @@ public class EditorGroup: UIViewController {
|
|
|
}
|
|
|
deleteReplyView()
|
|
|
deleteLinkPreview()
|
|
|
+ listMentionInTextField.removeAll()
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
|
|
|
self.tableChatView.scrollToBottom()
|
|
|
if self.markerCounter != nil {
|
|
@@ -1598,17 +1620,51 @@ extension EditorGroup: UIDocumentPickerDelegate, DocumentPickerDelegate, QLPrevi
|
|
|
|
|
|
extension EditorGroup: UITextViewDelegate {
|
|
|
public func textViewDidChangeSelection(_ textView: UITextView) {
|
|
|
- let cursorPositionInt = textView.selectedRange.location
|
|
|
+ lastPositionCursorMention = textView.selectedRange.location
|
|
|
+ let fulltextForMention = textView.text.substring(from: 0, to: lastPositionCursorMention - 1)
|
|
|
var isShowMention = false
|
|
|
- if cursorPositionInt > 0 {
|
|
|
- if textView.text.contains("@") {
|
|
|
- let fulltext = textView.text.substring(from: 0, to: cursorPositionInt - 1)
|
|
|
- let splitBreak = fulltext.components(separatedBy: "\n")
|
|
|
+ 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])
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ 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 {
|
|
|
+ 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
|
|
@@ -1636,6 +1692,9 @@ extension EditorGroup: UITextViewDelegate {
|
|
|
}
|
|
|
|
|
|
public func textViewDidChange(_ textView: UITextView) {
|
|
|
+ if textView.text.count == 0 {
|
|
|
+ isAlwaysHideLinkPreview = false
|
|
|
+ }
|
|
|
if allowTyping {
|
|
|
allowTyping = false
|
|
|
if dataTopic["chat_id"] as! String == "" {
|
|
@@ -1649,10 +1708,23 @@ extension EditorGroup: UITextViewDelegate {
|
|
|
self.allowTyping = true
|
|
|
})
|
|
|
}
|
|
|
- checkLink(fullText: textView.text)
|
|
|
- if textView.text.contains("*") || textView.text.contains("_") || textView.text.contains("^") || textView.text.contains("~") {
|
|
|
+ timerCheckLink?.invalidate()
|
|
|
+ timerCheckLink = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: {_ in
|
|
|
+ self.checkLink(fullText: textView.text)
|
|
|
+ })
|
|
|
+
|
|
|
+ if listMentionInTextField.count > 0 {
|
|
|
+ for j in 0..<listMentionInTextField.count {
|
|
|
+ let name = (listMentionInTextField[j].firstName + " " + listMentionInTextField[j].lastName).trimmingCharacters(in: .whitespaces)
|
|
|
+ if !textView.text.contains("@\(name)") {
|
|
|
+ listMentionInTextField.remove(at: j)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if textView.text.contains("*") || textView.text.contains("_") || textView.text.contains("^") || textView.text.contains("~") || textView.text.contains("@") {
|
|
|
textView.preserveCursorPosition(withChanges: { _ in
|
|
|
- textView.attributedText = textView.text.richText(isEditing: true)
|
|
|
+ textView.attributedText = textView.text.richText(isEditing: true, group_id: self.dataGroup["group_id"] as! String, listMentionInTextField: listMentionInTextField)
|
|
|
return .preserveCursor
|
|
|
})
|
|
|
}
|
|
@@ -1661,6 +1733,12 @@ extension EditorGroup: UITextViewDelegate {
|
|
|
private func showMention(text: String) {
|
|
|
if self.contraintBottomMention.constant < 0 {
|
|
|
self.contraintBottomMention.constant = 65 + ((self.keyboardHeightForMention != nil) ? self.keyboardHeightForMention! : 0)
|
|
|
+ if self.viewTextfield.subviews.contains(self.containerLink) {
|
|
|
+ self.contraintBottomMention.constant = self.contraintBottomMention.constant + 80
|
|
|
+ }
|
|
|
+ if self.viewTextfield.subviews.contains(self.containerPreviewReply) {
|
|
|
+ self.contraintBottomMention.constant = self.contraintBottomMention.constant + 50
|
|
|
+ }
|
|
|
UIView.animate(withDuration: 0.5, animations: {
|
|
|
self.view.layoutIfNeeded()
|
|
|
})
|
|
@@ -1668,15 +1746,23 @@ extension EditorGroup: UITextViewDelegate {
|
|
|
listMentionWithText.removeAll()
|
|
|
Database.shared.database?.inTransaction({ fmdb, rollback in
|
|
|
let idMe = UserDefaults.standard.string(forKey: "me")!
|
|
|
- if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "SELECT thumb_id, f_pin, first_name || ' ' || ifnull(last_name, '') name FROM GROUPZ_MEMBER where group_id='\(self.dataGroup["group_id"] as! String)' AND f_pin <> '\(idMe)' AND name LIKE '%\(text)%'") {
|
|
|
+ if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "SELECT f_pin, first_name || ' ' || ifnull(last_name, '') name FROM GROUPZ_MEMBER where group_id='\(self.dataGroup["group_id"] as! String)' AND f_pin <> '\(idMe)' AND name LIKE '%\(text)%'") {
|
|
|
while cursor.next() {
|
|
|
- listMentionWithText.append(User(pin: cursor.string(forColumnIndex: 1) ?? "",
|
|
|
- firstName: cursor.string(forColumnIndex: 2) ?? "",
|
|
|
- lastName: cursor.string(forColumnIndex: 2) ?? "",
|
|
|
- thumb: cursor.string(forColumnIndex: 0) ?? ""))
|
|
|
+ let user = User(pin: "")
|
|
|
+ user.pin = cursor.string(forColumnIndex: 0) ?? ""
|
|
|
+ user.firstName = cursor.string(forColumnIndex: 1) ?? ""
|
|
|
+ if !user.pin.isEmpty {
|
|
|
+ let userFromBuddy = User.getDataCanNil(pin: user.pin, fmdb: fmdb)
|
|
|
+ if userFromBuddy != nil {
|
|
|
+ listMentionWithText.append(userFromBuddy!)
|
|
|
+ } else {
|
|
|
+ listMentionWithText.append(user)
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
cursor.close()
|
|
|
}
|
|
|
+ listMentionWithText.removeAll(where: { listMentionInTextField.contains($0) })
|
|
|
if listMentionWithText.count > 0 {
|
|
|
if listMentionWithText.count < 5 {
|
|
|
self.heightTableMention.constant = CGFloat(44 * listMentionWithText.count)
|
|
@@ -1701,84 +1787,86 @@ extension EditorGroup: UITextViewDelegate {
|
|
|
}
|
|
|
|
|
|
private func checkLink(fullText: String) {
|
|
|
- var text = ""
|
|
|
- let listTextSplitBreak = fullText.components(separatedBy: "\n")
|
|
|
- let indexFirstLinkSplitBreak = listTextSplitBreak.firstIndex(where: { $0.contains("www.") || $0.contains("http://") || $0.contains("https://") })
|
|
|
- if indexFirstLinkSplitBreak != nil {
|
|
|
- let listTextSplitSpace = listTextSplitBreak[indexFirstLinkSplitBreak!].components(separatedBy: " ")
|
|
|
- let indexFirstLinkSplitSpace = listTextSplitSpace.firstIndex(where: { ($0.starts(with: "www.") && $0.components(separatedBy: ".").count > 2) || ($0.starts(with: "http://") && $0.components(separatedBy: ".").count > 1) || ($0.starts(with: "https://") && $0.components(separatedBy: ".").count > 1) })
|
|
|
- if indexFirstLinkSplitSpace != nil {
|
|
|
- text = listTextSplitSpace[indexFirstLinkSplitSpace!]
|
|
|
- }
|
|
|
- }
|
|
|
- if !text.isEmpty {
|
|
|
- var stringURl = text
|
|
|
- if stringURl.starts(with: "www.") {
|
|
|
- stringURl = "https://" + stringURl.replacingOccurrences(of: "www.", with: "")
|
|
|
- }
|
|
|
- var dataURL = ""
|
|
|
- Database.shared.database?.inTransaction({ (fmdb, rollback) in
|
|
|
- if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select data_link from LINK_PREVIEW where link='\(text)'") {
|
|
|
- while cursor.next() {
|
|
|
- if let data = cursor.string(forColumnIndex: 0) {
|
|
|
- dataURL = data
|
|
|
- }
|
|
|
- }
|
|
|
- cursor.close()
|
|
|
+ if !isAlwaysHideLinkPreview {
|
|
|
+ var text = ""
|
|
|
+ let listTextSplitBreak = fullText.components(separatedBy: "\n")
|
|
|
+ let indexFirstLinkSplitBreak = listTextSplitBreak.firstIndex(where: { $0.contains("www.") || $0.contains("http://") || $0.contains("https://") })
|
|
|
+ if indexFirstLinkSplitBreak != nil {
|
|
|
+ let listTextSplitSpace = listTextSplitBreak[indexFirstLinkSplitBreak!].components(separatedBy: " ")
|
|
|
+ let indexFirstLinkSplitSpace = listTextSplitSpace.firstIndex(where: { ($0.starts(with: "www.") && $0.components(separatedBy: ".").count > 2) || ($0.starts(with: "http://") && $0.components(separatedBy: ".").count > 1) || ($0.starts(with: "https://") && $0.components(separatedBy: ".").count > 1) })
|
|
|
+ if indexFirstLinkSplitSpace != nil {
|
|
|
+ text = listTextSplitSpace[indexFirstLinkSplitSpace!]
|
|
|
}
|
|
|
- })
|
|
|
- if !dataURL.isEmpty {
|
|
|
- if let data = try! JSONSerialization.jsonObject(with: dataURL.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
|
|
|
- let title = data["title"] as! String
|
|
|
- let description = data["description"] as! String
|
|
|
- let imageUrl = data["imageUrl"] as? String
|
|
|
- let link = data["link"] as! String
|
|
|
- if self.showingLink != text {
|
|
|
- self.showingLink = text
|
|
|
- self.deleteLinkPreview()
|
|
|
- self.buildPreviewLink(imageUrl: imageUrl, title: title, description: description, stringURl: link)
|
|
|
- }
|
|
|
+ }
|
|
|
+ if !text.isEmpty {
|
|
|
+ var stringURl = text
|
|
|
+ if stringURl.starts(with: "www.") {
|
|
|
+ stringURl = "https://" + stringURl.replacingOccurrences(of: "www.", with: "")
|
|
|
}
|
|
|
- } else {
|
|
|
- let urlData = URL(string: stringURl)!
|
|
|
- Readability.parse(url: urlData, completion: { data in
|
|
|
- if data != nil {
|
|
|
- let title = data!.title
|
|
|
- let description = data!.description
|
|
|
- let imageUrl = data!.topImage
|
|
|
- Database.shared.database?.inTransaction({ (fmdb, rollback) in
|
|
|
- do {
|
|
|
- var dataJson: [String: Any] = [:]
|
|
|
- dataJson["title"] = title
|
|
|
- dataJson["description"] = description
|
|
|
- dataJson["imageUrl"] = imageUrl
|
|
|
- dataJson["link"] = text
|
|
|
- guard let json = String(data: try! JSONSerialization.data(withJSONObject: dataJson, options: []), encoding: String.Encoding.utf8) else {
|
|
|
- return
|
|
|
- }
|
|
|
- _ = try Database.shared.insertRecord(fmdb: fmdb, table: "LINK_PREVIEW", cvalues: [
|
|
|
- "id" : "\(Date().currentTimeMillis().toHex())",
|
|
|
- "link" : text,
|
|
|
- "data_link" : json,
|
|
|
- "retry": 0
|
|
|
- ], replace: true)
|
|
|
- } catch {
|
|
|
- rollback.pointee = true
|
|
|
- print(error)
|
|
|
+ var dataURL = ""
|
|
|
+ Database.shared.database?.inTransaction({ (fmdb, rollback) in
|
|
|
+ if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select data_link from LINK_PREVIEW where link='\(text)'") {
|
|
|
+ while cursor.next() {
|
|
|
+ if let data = cursor.string(forColumnIndex: 0) {
|
|
|
+ dataURL = data
|
|
|
}
|
|
|
- })
|
|
|
+ }
|
|
|
+ cursor.close()
|
|
|
+ }
|
|
|
+ })
|
|
|
+ if !dataURL.isEmpty {
|
|
|
+ if let data = try! JSONSerialization.jsonObject(with: dataURL.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
|
|
|
+ let title = data["title"] as! String
|
|
|
+ let description = data["description"] as! String
|
|
|
+ let imageUrl = data["imageUrl"] as? String
|
|
|
+ let link = data["link"] as! String
|
|
|
if self.showingLink != text {
|
|
|
self.showingLink = text
|
|
|
self.deleteLinkPreview()
|
|
|
- self.buildPreviewLink(imageUrl: imageUrl, title: title, description: description, stringURl: text)
|
|
|
- } else {
|
|
|
- self.deleteLinkPreview()
|
|
|
+ self.buildPreviewLink(imageUrl: imageUrl, title: title, description: description, stringURl: link)
|
|
|
}
|
|
|
}
|
|
|
- })
|
|
|
+ } else {
|
|
|
+ let urlData = URL(string: stringURl)!
|
|
|
+ Readability.parse(url: urlData, completion: { data in
|
|
|
+ if data != nil {
|
|
|
+ let title = data!.title
|
|
|
+ let description = data!.description
|
|
|
+ let imageUrl = data!.topImage
|
|
|
+ Database.shared.database?.inTransaction({ (fmdb, rollback) in
|
|
|
+ do {
|
|
|
+ var dataJson: [String: Any] = [:]
|
|
|
+ dataJson["title"] = title
|
|
|
+ dataJson["description"] = description
|
|
|
+ dataJson["imageUrl"] = imageUrl
|
|
|
+ dataJson["link"] = text
|
|
|
+ guard let json = String(data: try! JSONSerialization.data(withJSONObject: dataJson, options: []), encoding: String.Encoding.utf8) else {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ _ = try Database.shared.insertRecord(fmdb: fmdb, table: "LINK_PREVIEW", cvalues: [
|
|
|
+ "id" : "\(Date().currentTimeMillis().toHex())",
|
|
|
+ "link" : text,
|
|
|
+ "data_link" : json,
|
|
|
+ "retry": 0
|
|
|
+ ], replace: true)
|
|
|
+ } catch {
|
|
|
+ rollback.pointee = true
|
|
|
+ print(error)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ if self.showingLink != text {
|
|
|
+ self.showingLink = text
|
|
|
+ self.deleteLinkPreview()
|
|
|
+ self.buildPreviewLink(imageUrl: imageUrl, title: title, description: description, stringURl: text)
|
|
|
+ } else {
|
|
|
+ self.deleteLinkPreview()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ deleteLinkPreview()
|
|
|
}
|
|
|
- } else {
|
|
|
- deleteLinkPreview()
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -1786,6 +1874,9 @@ extension EditorGroup: UITextViewDelegate {
|
|
|
if !self.viewTextfield.subviews.contains(self.containerLink){
|
|
|
UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
|
|
|
self.constraintTopTextField.constant = self.constraintTopTextField.constant + 80
|
|
|
+ if self.contraintBottomMention.constant > 0 {
|
|
|
+ self.contraintBottomMention.constant = self.contraintBottomMention.constant + 80
|
|
|
+ }
|
|
|
}, completion: nil)
|
|
|
}
|
|
|
|
|
@@ -1869,7 +1960,7 @@ extension EditorGroup: UITextViewDelegate {
|
|
|
cancelPreview.trailingAnchor.constraint(equalTo: self.containerLink.trailingAnchor, constant: -10).isActive = true
|
|
|
cancelPreview.centerYAnchor.constraint(equalTo: self.containerLink.centerYAnchor).isActive = true
|
|
|
cancelPreview.setImage(UIImage(systemName: "xmark.circle" , withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular, scale: .default)), for: .normal)
|
|
|
- cancelPreview.addTarget(nil, action: #selector(self.deleteLinkPreview), for: .touchUpInside)
|
|
|
+ cancelPreview.addTarget(nil, action: #selector(self.removeLinkPreviewUntilEmptyTextView), for: .touchUpInside)
|
|
|
cancelPreview.backgroundColor = .clear
|
|
|
cancelPreview.tintColor = .mainColor
|
|
|
}
|
|
@@ -1908,6 +1999,39 @@ extension EditorGroup: UITextViewDelegate {
|
|
|
}
|
|
|
|
|
|
public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
|
|
+ if text.isEmpty {
|
|
|
+ if listMentionInTextField.count > 0 {
|
|
|
+ for i in 0..<listMentionInTextField.count {
|
|
|
+ if lastPositionCursorMention == Int(listMentionInTextField[i].ex_block!)! {
|
|
|
+ let fulltextForMention = textFieldSend.text.substring(from: 0, to: lastPositionCursorMention - 1)
|
|
|
+ let diff = textFieldSend.text.count - fulltextForMention.count
|
|
|
+ var text = textView.text ?? ""
|
|
|
+ let nameMention = (listMentionInTextField[i].firstName + " " + listMentionInTextField[i].lastName).trimmingCharacters(in: .whitespaces)
|
|
|
+ let rangeReplacement = NSRange(location: lastPositionCursorMention - nameMention.count - 1, length: nameMention.count + 1)
|
|
|
+ let replacementText = ""
|
|
|
+
|
|
|
+ let copyAttributedText = text.richText(isEditing: true, group_id: self.dataGroup["group_id"] as! String, listMentionInTextField: listMentionInTextField)
|
|
|
+ copyAttributedText.removeAttribute(.foregroundColor, range: rangeReplacement)
|
|
|
+
|
|
|
+ textFieldSend.attributedText = copyAttributedText
|
|
|
+
|
|
|
+ // Replace the old text with the new text using the replaceSubrange(_:with:) method
|
|
|
+ if let startIndex = text.index(text.startIndex, offsetBy: rangeReplacement.location, limitedBy: text.endIndex),
|
|
|
+ let endIndex = text.index(startIndex, offsetBy: rangeReplacement.length, limitedBy: text.endIndex) {
|
|
|
+ text.replaceSubrange(startIndex..<endIndex, with: replacementText)
|
|
|
+ }
|
|
|
+
|
|
|
+ listMentionInTextField.remove(at: i)
|
|
|
+
|
|
|
+ textFieldSend.attributedText = text.richText(isEditing: true, group_id: self.dataGroup["group_id"] as! String, listMentionInTextField: listMentionInTextField)
|
|
|
+
|
|
|
+ let newPosition = textFieldSend.position(from: textFieldSend.beginningOfDocument, offset: textView.text.count - diff)
|
|
|
+ textFieldSend.selectedTextRange = textFieldSend.textRange(from: newPosition!, to: newPosition!)
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
if (self.textFieldSend.text.count == 0) {
|
|
|
return text != "\n"
|
|
|
}
|
|
@@ -2485,10 +2609,18 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
|
|
|
self.reffId = nil
|
|
|
UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
|
|
|
self.constraintTopTextField.constant = self.constraintTopTextField.constant - 50
|
|
|
+ if self.contraintBottomMention.constant > 0 {
|
|
|
+ self.contraintBottomMention.constant = self.contraintBottomMention.constant - 50
|
|
|
+ }
|
|
|
}, completion: nil)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ @objc func removeLinkPreviewUntilEmptyTextView() {
|
|
|
+ isAlwaysHideLinkPreview = true
|
|
|
+ deleteLinkPreview()
|
|
|
+ }
|
|
|
+
|
|
|
@objc func deleteLinkPreview() {
|
|
|
if self.containerLink.isDescendant(of: self.viewTextfield) {
|
|
|
self.containerLink.subviews.forEach { $0.removeFromSuperview() }
|
|
@@ -2496,6 +2628,9 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
|
|
|
self.containerLink.removeFromSuperview()
|
|
|
UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
|
|
|
self.constraintTopTextField.constant = self.constraintTopTextField.constant - 80
|
|
|
+ if self.contraintBottomMention.constant > 0 {
|
|
|
+ self.contraintBottomMention.constant = self.contraintBottomMention.constant - 80
|
|
|
+ }
|
|
|
}, completion: nil)
|
|
|
self.showingLink = ""
|
|
|
}
|
|
@@ -2624,7 +2759,37 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
|
|
|
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
|
|
if tableView == tableMention {
|
|
|
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: "@") {
|
|
|
+ listMentionInTextField.append(listMentionWithText[indexPath.row])
|
|
|
+ let indexIntMention = fulltextForMention.distance(from: fulltextForMention.startIndex, to: indexLastMention)
|
|
|
+ let rangeReplacement = NSRange(location: indexIntMention, length: lastPositionCursorMention - indexIntMention)
|
|
|
+
|
|
|
+ var addSpaceAfterReplacement = ""
|
|
|
+ if diff == 0 {
|
|
|
+ addSpaceAfterReplacement = " "
|
|
|
+ }
|
|
|
+
|
|
|
+ var text = textFieldSend.text ?? ""
|
|
|
+ let nameMention = (listMentionWithText[indexPath.row].firstName + " " + listMentionWithText[indexPath.row].lastName).trimmingCharacters(in: .whitespaces)
|
|
|
+ let replacementText = "@\(nameMention)"
|
|
|
+
|
|
|
+ // Replace the old text with the new text using the replaceSubrange(_:with:) method
|
|
|
+ if let startIndex = text.index(text.startIndex, offsetBy: rangeReplacement.location, limitedBy: text.endIndex),
|
|
|
+ let endIndex = text.index(startIndex, offsetBy: rangeReplacement.length, limitedBy: text.endIndex) {
|
|
|
+ text.replaceSubrange(startIndex..<endIndex, with: replacementText + addSpaceAfterReplacement)
|
|
|
+ }
|
|
|
+
|
|
|
+ textFieldSend.attributedText = text.richText(isEditing: true, group_id: self.dataGroup["group_id"] as! String, listMentionInTextField: listMentionInTextField)
|
|
|
+
|
|
|
+ let newPosition = textFieldSend.position(from: textFieldSend.beginningOfDocument, offset: textFieldSend.text.count - diff)
|
|
|
+ textFieldSend.selectedTextRange = textFieldSend.textRange(from: newPosition!, to: newPosition!)
|
|
|
+
|
|
|
+ listMentionInTextField.last?.ex_block = "\(textFieldSend.text.count - diff - addSpaceAfterReplacement.count)" //upperBound
|
|
|
+
|
|
|
+ hideMention()
|
|
|
+ }
|
|
|
return
|
|
|
}
|
|
|
let dataMessages = self.dataMessages.filter({ $0["chat_date"] as! String == dataDates[indexPath.section] })
|
|
@@ -2702,10 +2867,12 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
|
|
|
content.textProperties.font = UIFont.systemFont(ofSize: 11)
|
|
|
content.imageProperties.tintColor = .black
|
|
|
content.imageProperties.maximumSize = CGSize(width: 24, height: 24)
|
|
|
- getImage(name: listMentionWithText[indexPath.row].thumb, placeholderImage: UIImage(systemName: "person"), isCircle: true, tableView: tableView, indexPath: indexPath, completion: { result, isDownloaded, image in
|
|
|
- content.image = image
|
|
|
- })
|
|
|
- content.text = listMentionWithText[indexPath.row].firstName
|
|
|
+ if indexPath.row < listMentionWithText.count {
|
|
|
+ getImage(name: listMentionWithText[indexPath.row].thumb, placeholderImage: UIImage(systemName: "person"), isCircle: true, tableView: tableView, indexPath: indexPath, completion: { result, isDownloaded, image in
|
|
|
+ content.image = image
|
|
|
+ })
|
|
|
+ content.text = listMentionWithText[indexPath.row].firstName
|
|
|
+ }
|
|
|
cellMention.contentConfiguration = content
|
|
|
return cellMention
|
|
|
}
|
|
@@ -2727,11 +2894,6 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
|
|
|
profileMessage.addGestureRecognizer(tapGestureRecognizer)
|
|
|
|
|
|
let containerMessage = UIView()
|
|
|
- if !copySession && !forwardSession && !deleteSession && !isHistoryCC && !removed {
|
|
|
- let interaction = UIContextMenuInteraction(delegate: self)
|
|
|
- containerMessage.addInteraction(interaction)
|
|
|
- containerMessage.isUserInteractionEnabled = true
|
|
|
- }
|
|
|
|
|
|
let thumbChat = dataMessages[indexPath.row]["thumb_id"] as! String
|
|
|
let imageChat = dataMessages[indexPath.row]["image_id"] as! String
|
|
@@ -3116,28 +3278,43 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
|
|
|
imageSticker.contentMode = .scaleAspectFit
|
|
|
}
|
|
|
else {
|
|
|
- messageText.attributedText = textChat!.richText()
|
|
|
+ messageText.attributedText = textChat!.richText(group_id: self.dataGroup["group_id"] as! String)
|
|
|
}
|
|
|
} else {
|
|
|
- messageText.attributedText = textChat!.richText()
|
|
|
+ messageText.attributedText = textChat!.richText(group_id: self.dataGroup["group_id"] as! String)
|
|
|
}
|
|
|
|
|
|
- messageText.isUserInteractionEnabled = true
|
|
|
+ messageText.isUserInteractionEnabled = false
|
|
|
if !textChat!.isEmpty {
|
|
|
- let listText = textChat!.split(separator: " ")
|
|
|
- for i in 0...listText.count - 1 {
|
|
|
- if listText[i].lowercased().checkStartWithLink() {
|
|
|
- if ((listText[i].lowercased().starts(with: "www.") && listText[i].lowercased().split(separator: ".").count >= 3) || (!listText[i].lowercased().starts(with: "www.") && listText[i].lowercased().split(separator: ".").count >= 2)) && listText[i].lowercased().split(separator: ".").last!.count >= 2 {
|
|
|
- let objectGesture = ObjectGesture(target: self, action: #selector(tapMessageText(_:)))
|
|
|
- objectGesture.message_id = "\(listText[i])"
|
|
|
- messageText.addGestureRecognizer(objectGesture)
|
|
|
+ let listTextEnter = textChat!.split(separator: "\n")
|
|
|
+ for j in 0...listTextEnter.count - 1 {
|
|
|
+ let listText = listTextEnter[j].split(separator: " ")
|
|
|
+ for i in 0...listText.count - 1 {
|
|
|
+ if listText[i].lowercased().checkStartWithLink() {
|
|
|
+ let rangeTapLink = (textChat! as NSString).range(of: String(listText[i]))
|
|
|
+ // Add tap gesture recognizer to the range of text
|
|
|
+ let attributedString = textChat!.richText(group_id: self.dataGroup["group_id"] as! String)
|
|
|
+ attributedString.addAttributes([.foregroundColor: UIColor.blue, .underlineStyle: NSUnderlineStyle.single.rawValue], range: rangeTapLink)
|
|
|
+ messageText.attributedText = attributedString
|
|
|
+ if messageText.isUserInteractionEnabled == false && !copySession && !forwardSession && !deleteSession && !isHistoryCC && !removed {
|
|
|
+ messageText.isUserInteractionEnabled = true
|
|
|
+ let longPress = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPressLink(_:)))
|
|
|
+ longPress.minimumPressDuration = 0.1
|
|
|
+ containerMessage.addGestureRecognizer(longPress)
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ if !copySession && !forwardSession && !deleteSession && !isHistoryCC && !removed && messageText.isUserInteractionEnabled == false {
|
|
|
+ let interaction = UIContextMenuInteraction(delegate: self)
|
|
|
+ containerMessage.addInteraction(interaction)
|
|
|
+ containerMessage.isUserInteractionEnabled = true
|
|
|
+ }
|
|
|
+
|
|
|
if isSearching && textSearch.count > 1 {
|
|
|
- messageText.attributedText = textChat!.richText(isSearching: true, textSearch: textSearch)
|
|
|
+ messageText.attributedText = textChat!.richText(isSearching: true, textSearch: textSearch, group_id: self.dataGroup["group_id"] as! String)
|
|
|
if textChat!.lowercased().contains(textSearch) {
|
|
|
countMatchesSearch += 1
|
|
|
}
|
|
@@ -3368,6 +3545,120 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ let containerLinkMessage = UIView()
|
|
|
+ if thumbChat.isEmpty && fileChat.isEmpty && !textChat!.isEmpty {
|
|
|
+ var text = ""
|
|
|
+ let listTextSplitBreak = textChat!.components(separatedBy: "\n")
|
|
|
+ let indexFirstLinkSplitBreak = listTextSplitBreak.firstIndex(where: { $0.contains("www.") || $0.contains("http://") || $0.contains("https://") })
|
|
|
+ if indexFirstLinkSplitBreak != nil {
|
|
|
+ let listTextSplitSpace = listTextSplitBreak[indexFirstLinkSplitBreak!].components(separatedBy: " ")
|
|
|
+ let indexFirstLinkSplitSpace = listTextSplitSpace.firstIndex(where: { ($0.starts(with: "www.") && $0.components(separatedBy: ".").count > 2) || ($0.starts(with: "http://") && $0.components(separatedBy: ".").count > 1) || ($0.starts(with: "https://") && $0.components(separatedBy: ".").count > 1) })
|
|
|
+ if indexFirstLinkSplitSpace != nil {
|
|
|
+ text = listTextSplitSpace[indexFirstLinkSplitSpace!]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if !text.isEmpty {
|
|
|
+ var dataURL = ""
|
|
|
+ Database.shared.database?.inTransaction({ (fmdb, rollback) in
|
|
|
+ if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select data_link from LINK_PREVIEW where link='\(text)'"), cursor.next() {
|
|
|
+ if let data = cursor.string(forColumnIndex: 0) {
|
|
|
+ dataURL = data
|
|
|
+ }
|
|
|
+ cursor.close()
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ if !dataURL.isEmpty {
|
|
|
+ if let data = try! JSONSerialization.jsonObject(with: dataURL.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
|
|
|
+ let title = data["title"] as! String
|
|
|
+ let description = data["description"] as! String
|
|
|
+ let imageUrl = data["imageUrl"] as? String
|
|
|
+ let link = data["link"] as! String
|
|
|
+
|
|
|
+ topMarginText.constant = topMarginText.constant + 80
|
|
|
+
|
|
|
+ containerMessage.addSubview(containerLinkMessage)
|
|
|
+ containerLinkMessage.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ containerLinkMessage.leadingAnchor.constraint(equalTo:containerMessage.leadingAnchor, constant: 15).isActive = true
|
|
|
+ if dataMessages[indexPath.row]["attachment_flag"] as? String == "11" {
|
|
|
+ containerLinkMessage.bottomAnchor.constraint(equalTo: imageSticker.topAnchor, constant: -5).isActive = true
|
|
|
+ } else {
|
|
|
+ containerLinkMessage.bottomAnchor.constraint(equalTo: messageText.topAnchor, constant: -5).isActive = true
|
|
|
+ }
|
|
|
+ containerLinkMessage.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
|
|
|
+ containerLinkMessage.heightAnchor.constraint(equalToConstant: 80.0).isActive = true
|
|
|
+ containerLinkMessage.backgroundColor = .gray.withAlphaComponent(0.2)
|
|
|
+
|
|
|
+ let imagePreview = UIImageView()
|
|
|
+ if imageUrl != nil {
|
|
|
+ containerLinkMessage.addSubview(imagePreview)
|
|
|
+ imagePreview.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ imagePreview.leadingAnchor.constraint(equalTo: containerLinkMessage.leadingAnchor).isActive = true
|
|
|
+ imagePreview.bottomAnchor.constraint(equalTo: containerLinkMessage.bottomAnchor).isActive = true
|
|
|
+ imagePreview.topAnchor.constraint(equalTo: containerLinkMessage.topAnchor).isActive = true
|
|
|
+ imagePreview.widthAnchor.constraint(equalToConstant: 80.0).isActive = true
|
|
|
+ if !imageUrl!.starts(with: "https://") {
|
|
|
+ imagePreview.loadImageAsync(with: "https://www.google.be" + imageUrl!)
|
|
|
+ } else {
|
|
|
+ imagePreview.loadImageAsync(with: imageUrl)
|
|
|
+ }
|
|
|
+ imagePreview.contentMode = .scaleAspectFit
|
|
|
+ }
|
|
|
+
|
|
|
+ let titlePreview = UILabel()
|
|
|
+ containerLinkMessage.addSubview(titlePreview)
|
|
|
+ titlePreview.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ if imageUrl != nil {
|
|
|
+ titlePreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
|
|
|
+ } else {
|
|
|
+ titlePreview.leadingAnchor.constraint(equalTo: containerLinkMessage.leadingAnchor, constant: 5.0).isActive = true
|
|
|
+ }
|
|
|
+ titlePreview.topAnchor.constraint(equalTo: containerLinkMessage.topAnchor, constant: 25.0).isActive = true
|
|
|
+ titlePreview.trailingAnchor.constraint(equalTo: containerLinkMessage.trailingAnchor, constant: -80.0).isActive = true
|
|
|
+ titlePreview.text = title
|
|
|
+ titlePreview.font = UIFont.systemFont(ofSize: 14.0, weight: .bold)
|
|
|
+ titlePreview.textColor = .black
|
|
|
+
|
|
|
+ let descPreview = UILabel()
|
|
|
+ containerLinkMessage.addSubview(descPreview)
|
|
|
+ descPreview.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ if imageUrl != nil {
|
|
|
+ descPreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
|
|
|
+ } else {
|
|
|
+ descPreview.leadingAnchor.constraint(equalTo: containerLinkMessage.leadingAnchor, constant: 5.0).isActive = true
|
|
|
+ }
|
|
|
+ descPreview.topAnchor.constraint(equalTo: titlePreview.bottomAnchor).isActive = true
|
|
|
+ descPreview.trailingAnchor.constraint(equalTo: containerLinkMessage.trailingAnchor, constant: -80.0).isActive = true
|
|
|
+ descPreview.text = description
|
|
|
+ descPreview.font = UIFont.systemFont(ofSize: 12.0)
|
|
|
+ descPreview.textColor = .gray
|
|
|
+ descPreview.numberOfLines = 1
|
|
|
+
|
|
|
+ let linkPreview = UILabel()
|
|
|
+ containerLinkMessage.addSubview(linkPreview)
|
|
|
+ linkPreview.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ if imageUrl != nil {
|
|
|
+ linkPreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
|
|
|
+ } else {
|
|
|
+ linkPreview.leadingAnchor.constraint(equalTo: containerLinkMessage.leadingAnchor, constant: 5.0).isActive = true
|
|
|
+ }
|
|
|
+ linkPreview.topAnchor.constraint(equalTo: descPreview.bottomAnchor, constant: 8.0).isActive = true
|
|
|
+ linkPreview.trailingAnchor.constraint(equalTo: containerLinkMessage.trailingAnchor, constant: -80.0).isActive = true
|
|
|
+ linkPreview.text = link
|
|
|
+ linkPreview.font = UIFont.systemFont(ofSize: 10.0)
|
|
|
+ linkPreview.textColor = .gray
|
|
|
+ linkPreview.numberOfLines = 1
|
|
|
+
|
|
|
+ if !copySession && !forwardSession && !deleteSession {
|
|
|
+ let objectTap = ObjectGesture(target: self, action: #selector(tapMessageText(_:)))
|
|
|
+ objectTap.message_id = text
|
|
|
+ containerLinkMessage.addGestureRecognizer(objectTap)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
if (reffChat != "") {
|
|
|
let data = queryMessageReply(message_id: reffChat)
|
|
|
if data.count != 0 {
|
|
@@ -3382,6 +3673,8 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
|
|
|
containerReply.bottomAnchor.constraint(equalTo: imageThumb.topAnchor, constant: -5).isActive = true
|
|
|
} else if fileChat != "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as! String != "1") {
|
|
|
containerReply.bottomAnchor.constraint(equalTo: containerViewFile.topAnchor, constant: -5).isActive = true
|
|
|
+ } else if containerMessage.subviews.contains(containerLinkMessage) {
|
|
|
+ containerReply.bottomAnchor.constraint(equalTo: containerLinkMessage.topAnchor, constant: -5).isActive = true
|
|
|
} else if dataMessages[indexPath.row]["attachment_flag"] as? String == "11" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as! String != "1") {
|
|
|
containerReply.bottomAnchor.constraint(equalTo: imageSticker.topAnchor, constant: -5).isActive = true
|
|
|
} else {
|
|
@@ -3450,18 +3743,18 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
|
|
|
let file_chat = data["file_id"] as! String
|
|
|
if (attachment_flag == "0" && thumb_chat == "") {
|
|
|
contentReply.trailingAnchor.constraint(equalTo: containerReply.trailingAnchor, constant: -20).isActive = true
|
|
|
- contentReply.attributedText = message_text.richText()
|
|
|
+ contentReply.attributedText = message_text.richText(group_id: self.dataGroup["group_id"] as! String)
|
|
|
} else if (attachment_flag == "1" || image_chat != "") {
|
|
|
if (message_text == "") {
|
|
|
contentReply.text = "📷 Photo".localized()
|
|
|
} else {
|
|
|
- contentReply.attributedText = message_text.richText()
|
|
|
+ contentReply.attributedText = message_text.richText(group_id: self.dataGroup["group_id"] as! String)
|
|
|
}
|
|
|
} else if (attachment_flag == "2" || video_chat != "") {
|
|
|
if (message_text == "") {
|
|
|
contentReply.text = "📹 Video".localized()
|
|
|
} else {
|
|
|
- contentReply.attributedText = message_text.richText()
|
|
|
+ contentReply.attributedText = message_text.richText(group_id: self.dataGroup["group_id"] as! String)
|
|
|
}
|
|
|
} else if (attachment_flag == "6" || file_chat != ""){
|
|
|
contentReply.trailingAnchor.constraint(equalTo: containerReply.trailingAnchor, constant: -20).isActive = true
|
|
@@ -3798,6 +4091,118 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ @objc func handleLongPressLink(_ gestureRecognizer: UILongPressGestureRecognizer) {
|
|
|
+ func showMenuContext() {
|
|
|
+ if gestureRecognizer.state == .cancelled || gestureRecognizer.state == .ended{
|
|
|
+ timerCheckLink?.invalidate()
|
|
|
+ } else if gestureRecognizer.state == .began {
|
|
|
+ timerCheckLink = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: false, block: {_ in
|
|
|
+ let interaction = UIContextMenuInteraction(delegate: self)
|
|
|
+ gestureRecognizer.view!.addInteraction(interaction)
|
|
|
+ guard let interaction = gestureRecognizer.view!.interactions.first,
|
|
|
+ let data = Data(base64Encoded: "X3ByZXNlbnRNZW51QXRMb2NhdGlvbjo="),
|
|
|
+ let str = String(data: data, encoding: .utf8)
|
|
|
+ else {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ let selector = NSSelectorFromString(str)
|
|
|
+ guard interaction.responds(to: selector) else {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
|
|
|
+ impactHeavy.impactOccurred()
|
|
|
+ interaction.perform(selector, with: self.view)
|
|
|
+ self.isLinkCopied = true
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if gestureRecognizer.state == .began {
|
|
|
+ let touchPoint = gestureRecognizer.location(in: self.view)
|
|
|
+ touchedSubview = self.view.hitTest(touchPoint, with: nil) ?? UIView()
|
|
|
+ if !(touchedSubview is UILabel) {
|
|
|
+ showMenuContext()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ guard let label = touchedSubview as? UILabel else { return }
|
|
|
+ let touchPointLabel = gestureRecognizer.location(in: label)
|
|
|
+
|
|
|
+ if let text = label.text, let range = getWordRange(at: touchPointLabel, in: label) {
|
|
|
+ let word = String(text[range])
|
|
|
+ if word.starts(with: "www.") || word.starts(with: "https://") || word.starts(with: "http://") {
|
|
|
+ if gestureRecognizer.state == .cancelled || gestureRecognizer.state == .ended{
|
|
|
+ timerCheckLink?.invalidate()
|
|
|
+ if !isLinkCopied {
|
|
|
+ var stringURl = word
|
|
|
+ if stringURl.starts(with: "www.") {
|
|
|
+ stringURl = "https://" + stringURl.replacingOccurrences(of: "www.", with: "")
|
|
|
+ }
|
|
|
+ guard let url = URL(string: stringURl) else { return }
|
|
|
+ UIApplication.shared.open(url)
|
|
|
+ label.attributedText = removeHighlightedText(for: text, in: range, label: label)
|
|
|
+ }
|
|
|
+ isLinkCopied = false
|
|
|
+ } else if gestureRecognizer.state == .began {
|
|
|
+ label.attributedText = highlightedText(for: text, in: range, label: label)
|
|
|
+ timerCheckLink = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: {_ in
|
|
|
+ self.isLinkCopied = true
|
|
|
+ UIPasteboard.general.string = word
|
|
|
+ self.showToast(message: "Link Copied".localized(), font: UIFont.systemFont(ofSize: 12, weight: .medium), controller: self)
|
|
|
+ label.attributedText = self.removeHighlightedText(for: text, in: range, label: label)
|
|
|
+ self.timerCheckLink?.invalidate()
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ showMenuContext()
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ showMenuContext()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ func getWordRange(at point: CGPoint, in label: UILabel) -> Range<String.Index>? {
|
|
|
+ guard let text = label.text else { return nil }
|
|
|
+
|
|
|
+ let layoutManager = NSLayoutManager()
|
|
|
+ let textContainer = NSTextContainer(size: label.frame.size)
|
|
|
+ let textStorage = NSTextStorage(attributedString: NSAttributedString(string: text))
|
|
|
+
|
|
|
+ layoutManager.addTextContainer(textContainer)
|
|
|
+ textStorage.addLayoutManager(layoutManager)
|
|
|
+ textContainer.lineFragmentPadding = 0
|
|
|
+ textContainer.maximumNumberOfLines = label.numberOfLines
|
|
|
+
|
|
|
+ lastTouchPoint = point
|
|
|
+ let characterIndex = layoutManager.characterIndex(for: point, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
|
|
|
+
|
|
|
+ if characterIndex == text.count - 1 {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ var wordStartIndex = characterIndex
|
|
|
+ while wordStartIndex > 0 && text[text.index(text.startIndex, offsetBy: wordStartIndex - 1)] != " " && text[text.index(text.startIndex, offsetBy: wordStartIndex - 1)] != "\n" {
|
|
|
+ wordStartIndex -= 1
|
|
|
+ }
|
|
|
+
|
|
|
+ var wordEndIndex = characterIndex
|
|
|
+ while wordEndIndex < text.count && text[text.index(text.startIndex, offsetBy: wordEndIndex)] != " " && text[text.index(text.startIndex, offsetBy: wordEndIndex)] != "\n" {
|
|
|
+ wordEndIndex += 1
|
|
|
+ }
|
|
|
+
|
|
|
+ return text.index(text.startIndex, offsetBy: wordStartIndex)..<text.index(text.startIndex, offsetBy: wordEndIndex)
|
|
|
+ }
|
|
|
+
|
|
|
+ func highlightedText(for text: String, in range: Range<String.Index>, label: UILabel) -> NSAttributedString {
|
|
|
+ let mutableAttributedString = label.attributedText!.mutableCopy() as! NSMutableAttributedString
|
|
|
+ mutableAttributedString.addAttribute(.backgroundColor, value: UIColor.lightGray.withAlphaComponent(0.5), range: NSRange(range, in: text))
|
|
|
+ return mutableAttributedString
|
|
|
+ }
|
|
|
+
|
|
|
+ func removeHighlightedText(for text: String, in range: Range<String.Index>, label: UILabel) -> NSAttributedString {
|
|
|
+ let mutableAttributedString = label.attributedText!.mutableCopy() as! NSMutableAttributedString
|
|
|
+ mutableAttributedString.removeAttribute(.backgroundColor, range: NSRange(range, in: text))
|
|
|
+ return mutableAttributedString
|
|
|
+ }
|
|
|
+
|
|
|
@objc func tapMessageText(_ sender: ObjectGesture) {
|
|
|
var stringURl = sender.message_id.lowercased()
|
|
|
if stringURl.starts(with: "www.") {
|
|
@@ -3840,6 +4245,9 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
|
|
|
self.reffId = dataMessages[indexPath.row]["message_id"] as? String
|
|
|
UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
|
|
|
self.constraintTopTextField.constant = self.constraintTopTextField.constant + 50
|
|
|
+ if self.contraintBottomMention.constant > 0 {
|
|
|
+ self.contraintBottomMention.constant = self.contraintBottomMention.constant + 50
|
|
|
+ }
|
|
|
}, completion: nil)
|
|
|
if (self.currentIndexpath != nil) {
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
|
|
@@ -3903,18 +4311,18 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
|
|
|
let video_chat = dataMessages[indexPath.row]["video_id"] as! String
|
|
|
let file_chat = dataMessages[indexPath.row]["file_id"] as! String
|
|
|
if (attachment_flag == "0" && thumb_chat == "") {
|
|
|
- contentReply.attributedText = message_text.richText()
|
|
|
+ contentReply.attributedText = message_text.richText(group_id: self.dataGroup["group_id"] as! String)
|
|
|
} else if (attachment_flag == "1" || image_chat != "") {
|
|
|
if (message_text == "") {
|
|
|
contentReply.text = "📷 Photo".localized()
|
|
|
} else {
|
|
|
- contentReply.attributedText = message_text.richText()
|
|
|
+ contentReply.attributedText = message_text.richText(group_id: self.dataGroup["group_id"] as! String)
|
|
|
}
|
|
|
} else if (attachment_flag == "2" || video_chat != "") {
|
|
|
if (message_text == "") {
|
|
|
contentReply.text = "📹 Video".localized()
|
|
|
} else {
|
|
|
- contentReply.attributedText = message_text.richText()
|
|
|
+ contentReply.attributedText = message_text.richText(group_id: self.dataGroup["group_id"] as! String)
|
|
|
}
|
|
|
} else if (attachment_flag == "6" || file_chat != ""){
|
|
|
contentReply.text = "📄 \(message_text.components(separatedBy: "|")[0])"
|