EditorStarMessages.swift 91 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502
  1. //
  2. // EditorStarMessages.swift
  3. // Qmera
  4. //
  5. // Created by Akhmad Al Qindi Irsyam on 22/09/21.
  6. //
  7. import UIKit
  8. import AVKit
  9. import AVFoundation
  10. import QuickLook
  11. import Photos
  12. import SwiftLinkPreview
  13. public class EditorStarMessages: UIViewController, UITableViewDataSource, UITableViewDelegate, UIContextMenuInteractionDelegate, QLPreviewControllerDataSource {
  14. @IBOutlet var tableChatView: UITableView!
  15. var dataMessages: [[String: Any?]] = []
  16. var dataDates: [String] = []
  17. var previewItem = NSURL()
  18. var fromNotification = false
  19. var timerCheckLink: Timer?
  20. var showMenuContext = false
  21. var touchedSubview = UIView()
  22. var lastTouchPoint: CGPoint = .zero
  23. public override func viewDidLoad() {
  24. super.viewDidLoad()
  25. if fromNotification {
  26. let imageButton = UIImageView(frame: CGRect(x: -16, y: 0, width: 20, height: 44))
  27. imageButton.image = UIImage(systemName: "chevron.backward", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular, scale: .default))?.withTintColor(.white)
  28. imageButton.contentMode = .left
  29. let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapExit))
  30. imageButton.isUserInteractionEnabled = true
  31. imageButton.addGestureRecognizer(tapGestureRecognizer)
  32. let leftItem = UIBarButtonItem(customView: imageButton)
  33. self.navigationItem.leftBarButtonItem = leftItem
  34. }
  35. navigationController?.navigationBar.isTranslucent = false
  36. navigationController?.navigationBar.barTintColor = UIColor.mainColor
  37. navigationController?.navigationBar.tintColor = .white
  38. navigationController?.navigationBar.topItem?.title = ""
  39. self.title = "Favorite Messages".localized()
  40. let menu = UIMenu(title: "", children: [
  41. UIAction(title: "Unfavorite all messages".localized(), handler: {(_) in
  42. DispatchQueue.global().async {
  43. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  44. _ = Database.shared.updateAllRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  45. "is_stared" : 0
  46. ])
  47. })
  48. }
  49. self.dataMessages.removeAll()
  50. self.tableChatView.reloadData()
  51. }),
  52. ])
  53. getData()
  54. let moreIcon = UIBarButtonItem(image: UIImage(systemName: "ellipsis"), menu: menu)
  55. navigationItem.rightBarButtonItem = moreIcon
  56. navigationItem.rightBarButtonItem?.tintColor = UIColor.secondaryColor
  57. tableChatView.delegate = self
  58. tableChatView.dataSource = self
  59. tableChatView.reloadData()
  60. let center: NotificationCenter = NotificationCenter.default
  61. center.addObserver(self, selector: #selector(onStatusChat(notification:)), name: NSNotification.Name(rawValue: Nexilis.listenerStatusChat), object: nil)
  62. }
  63. @objc func didTapExit() {
  64. self.dismiss(animated: true, completion: nil)
  65. }
  66. @objc func onStatusChat(notification: NSNotification) {
  67. DispatchQueue.main.async {
  68. let data:[AnyHashable : Any] = notification.userInfo!
  69. if let dataMessage = data["message"] as? TMessage {
  70. let chatData = dataMessage.mBodies
  71. if (chatData.keys.contains(CoreMessage_TMessageKey.MESSAGE_ID) && !(chatData[CoreMessage_TMessageKey.MESSAGE_ID]!).contains("-2,")) {
  72. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == chatData[CoreMessage_TMessageKey.MESSAGE_ID]! })
  73. if (idx != nil) {
  74. if (chatData[CoreMessage_TMessageKey.DELETE_MESSAGE_FLAG] == "1") {
  75. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as! String)
  76. let row = self.dataMessages.filter({ $0["chat_date"] as! String == self.dataMessages[idx!]["chat_date"] as! String}).firstIndex(where: { $0["message_id"] as? String == self.dataMessages[idx!]["message_id"] as? String })
  77. self.dataMessages.remove(at: idx!)
  78. if row != nil && section != nil {
  79. let indexPath = IndexPath(row: row!, section: section!)
  80. self.tableChatView.deleteRows(at: [indexPath], with: .fade)
  81. if self.dataMessages.filter({ $0["chat_date"] as! String == self.dataMessages[indexPath.row]["chat_date"] as! String }).count == 0 {
  82. self.dataDates.remove(at: indexPath.section)
  83. self.tableChatView.deleteSections(IndexSet(integer: indexPath.section), with: .fade)
  84. }
  85. }
  86. }
  87. }
  88. }
  89. else if (chatData.keys.contains("message_id")) {
  90. let idx = self.dataMessages.firstIndex(where: { "'\(String(describing: $0["message_id"] as? String))'" == chatData["message_id"]! })
  91. if (idx != nil) {
  92. if (chatData[CoreMessage_TMessageKey.DELETE_MESSAGE_FLAG] == "1") {
  93. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as! String)
  94. let row = self.dataMessages.filter({ $0["chat_date"] as! String == self.dataMessages[idx!]["chat_date"] as! String}).firstIndex(where: { $0["message_id"] as? String == self.dataMessages[idx!]["message_id"] as? String })
  95. self.dataMessages.remove(at: idx!)
  96. if row != nil && section != nil {
  97. let indexPath = IndexPath(row: row!, section: section!)
  98. self.tableChatView.deleteRows(at: [indexPath], with: .fade)
  99. if self.dataMessages.filter({ $0["chat_date"] as! String == self.dataMessages[indexPath.row]["chat_date"] as! String }).count == 0 {
  100. self.dataDates.remove(at: indexPath.section)
  101. self.tableChatView.deleteSections(IndexSet(integer: indexPath.section), with: .fade)
  102. }
  103. }
  104. }
  105. }
  106. }
  107. }
  108. }
  109. }
  110. public func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
  111. let containerView = UIView()
  112. containerView.backgroundColor = .clear
  113. let dateView = UIView()
  114. containerView.addSubview(dateView)
  115. dateView.translatesAutoresizingMaskIntoConstraints = false
  116. var topAnchor = dateView.topAnchor.constraint(equalTo: containerView.topAnchor)
  117. topAnchor = dateView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 10.0)
  118. NSLayoutConstraint.activate([
  119. topAnchor,
  120. dateView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
  121. dateView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
  122. dateView.heightAnchor.constraint(equalToConstant: 30),
  123. dateView.widthAnchor.constraint(greaterThanOrEqualToConstant: 60)
  124. ])
  125. dateView.backgroundColor = .orangeColor
  126. dateView.layer.cornerRadius = 15.0
  127. dateView.clipsToBounds = true
  128. let labelDate = UILabel()
  129. dateView.addSubview(labelDate)
  130. labelDate.translatesAutoresizingMaskIntoConstraints = false
  131. NSLayoutConstraint.activate([
  132. labelDate.centerYAnchor.constraint(equalTo: dateView.centerYAnchor),
  133. labelDate.centerXAnchor.constraint(equalTo: dateView.centerXAnchor),
  134. labelDate.leadingAnchor.constraint(equalTo: dateView.leadingAnchor, constant: 10),
  135. labelDate.trailingAnchor.constraint(equalTo: dateView.trailingAnchor, constant: -10),
  136. ])
  137. labelDate.textAlignment = .center
  138. labelDate.textColor = .secondaryColor
  139. labelDate.font = UIFont.systemFont(ofSize: 12, weight: .medium)
  140. labelDate.text = dataDates[section]
  141. return containerView
  142. }
  143. public func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
  144. return 40
  145. }
  146. public func numberOfSections(in tableView: UITableView) -> Int {
  147. dataDates.count
  148. }
  149. public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  150. let count = dataMessages.filter({ $0["chat_date"] as! String == dataDates[section] }).count
  151. return count
  152. }
  153. public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  154. let idMe = UserDefaults.standard.string(forKey: "me") as String?
  155. let dataMessages = dataMessages.filter({$0["chat_date"] as! String == dataDates[indexPath.section]})
  156. let cellMessage = UITableViewCell()
  157. cellMessage.backgroundColor = .clear
  158. cellMessage.selectionStyle = .none
  159. let profileMessage = UIImageView()
  160. profileMessage.frame.size = CGSize(width: 35, height: 35)
  161. cellMessage.contentView.addSubview(profileMessage)
  162. profileMessage.translatesAutoresizingMaskIntoConstraints = false
  163. let containerMessage = UIView()
  164. let interaction = UIContextMenuInteraction(delegate: self)
  165. containerMessage.addInteraction(interaction)
  166. containerMessage.isUserInteractionEnabled = true
  167. cellMessage.contentView.addSubview(containerMessage)
  168. containerMessage.translatesAutoresizingMaskIntoConstraints = false
  169. let timeMessage = UILabel()
  170. cellMessage.contentView.addSubview(timeMessage)
  171. timeMessage.translatesAutoresizingMaskIntoConstraints = false
  172. timeMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
  173. let messageText = UILabel()
  174. containerMessage.addSubview(messageText)
  175. messageText.translatesAutoresizingMaskIntoConstraints = false
  176. let topMarginText = messageText.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 32)
  177. let dataProfile = getDataProfile(f_pin: dataMessages[indexPath.row]["f_pin"] as! String)
  178. let statusMessage = UIImageView()
  179. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  180. profileMessage.topAnchor.constraint(equalTo: cellMessage.contentView.topAnchor, constant: 5).isActive = true
  181. profileMessage.trailingAnchor.constraint(equalTo: cellMessage.contentView.trailingAnchor, constant: -15).isActive = true
  182. profileMessage.heightAnchor.constraint(equalToConstant: 37).isActive = true
  183. profileMessage.widthAnchor.constraint(equalToConstant: 35).isActive = true
  184. profileMessage.circle()
  185. profileMessage.clipsToBounds = true
  186. profileMessage.backgroundColor = .lightGray
  187. profileMessage.image = UIImage(systemName: "person")
  188. profileMessage.tintColor = .white
  189. profileMessage.contentMode = .scaleAspectFit
  190. let pictureImage = dataProfile["image_id"]
  191. if (pictureImage != "" && pictureImage != nil) {
  192. profileMessage.setImage(name: pictureImage!)
  193. profileMessage.contentMode = .scaleAspectFill
  194. }
  195. containerMessage.topAnchor.constraint(equalTo: cellMessage.contentView.topAnchor, constant: 5).isActive = true
  196. containerMessage.leadingAnchor.constraint(greaterThanOrEqualTo: cellMessage.contentView.leadingAnchor, constant: 80).isActive = true
  197. containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
  198. containerMessage.trailingAnchor.constraint(equalTo: profileMessage.leadingAnchor, constant: -5).isActive = true
  199. containerMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
  200. if (dataMessages[indexPath.row]["attachment_flag"] as? String == "11" && dataMessages[indexPath.row]["reff_id"]as? String == "") {
  201. containerMessage.backgroundColor = .clear
  202. } else {
  203. containerMessage.backgroundColor = .mainColor
  204. }
  205. containerMessage.layer.cornerRadius = 10.0
  206. containerMessage.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner, .layerMinXMinYCorner]
  207. containerMessage.clipsToBounds = true
  208. timeMessage.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: -8).isActive = true
  209. cellMessage.contentView.addSubview(statusMessage)
  210. statusMessage.translatesAutoresizingMaskIntoConstraints = false
  211. statusMessage.bottomAnchor.constraint(equalTo: timeMessage.topAnchor).isActive = true
  212. statusMessage.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: -8).isActive = true
  213. statusMessage.widthAnchor.constraint(equalToConstant: 15).isActive = true
  214. statusMessage.heightAnchor.constraint(equalToConstant: 15).isActive = true
  215. if (dataMessages[indexPath.row]["status"]! as! String == "1" || dataMessages[indexPath.row]["status"]! as! String == "2" ) {
  216. statusMessage.image = UIImage(named: "checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
  217. } else if (dataMessages[indexPath.row]["status"]! as! String == "3") {
  218. statusMessage.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
  219. } else {
  220. statusMessage.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.systemBlue)
  221. }
  222. let nameSender = UILabel()
  223. containerMessage.addSubview(nameSender)
  224. nameSender.translatesAutoresizingMaskIntoConstraints = false
  225. nameSender.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 15).isActive = true
  226. nameSender.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  227. nameSender.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  228. nameSender.font = UIFont.systemFont(ofSize: 12).bold
  229. nameSender.text = dataProfile["name"]
  230. nameSender.textAlignment = .right
  231. if (dataMessages[indexPath.row]["attachment_flag"] as? String == "11" && dataMessages[indexPath.row]["reff_id"]as? String == "") {
  232. containerMessage.backgroundColor = .clear
  233. nameSender.textColor = .mainColor
  234. } else {
  235. containerMessage.backgroundColor = .mainColor
  236. nameSender.textColor = .white
  237. }
  238. } else {
  239. profileMessage.topAnchor.constraint(equalTo: cellMessage.contentView.topAnchor, constant: 5).isActive = true
  240. profileMessage.leadingAnchor.constraint(equalTo: cellMessage.contentView.leadingAnchor, constant: 15).isActive = true
  241. profileMessage.heightAnchor.constraint(equalToConstant: 37).isActive = true
  242. profileMessage.widthAnchor.constraint(equalToConstant: 35).isActive = true
  243. profileMessage.circle()
  244. profileMessage.clipsToBounds = true
  245. profileMessage.backgroundColor = .lightGray
  246. profileMessage.image = UIImage(systemName: "person")
  247. profileMessage.tintColor = .white
  248. profileMessage.contentMode = .scaleAspectFit
  249. let pictureImage = dataProfile["image_id"]
  250. if dataMessages[indexPath.row]["f_pin"] as! String == "-999" {
  251. if !Utils.getIconDock().isEmpty {
  252. let dataImage = try? Data(contentsOf: URL(string: Utils.getUrlDock()!)!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
  253. if dataImage != nil {
  254. profileMessage.image = UIImage(data: dataImage!)
  255. }
  256. } else {
  257. profileMessage.image = UIImage(named: "pb_button", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
  258. }
  259. profileMessage.contentMode = .scaleAspectFit
  260. }
  261. if (pictureImage != "" && pictureImage != nil) {
  262. profileMessage.setImage(name: pictureImage!)
  263. profileMessage.contentMode = .scaleAspectFill
  264. }
  265. containerMessage.topAnchor.constraint(equalTo: cellMessage.contentView.topAnchor, constant: 5).isActive = true
  266. containerMessage.leadingAnchor.constraint(equalTo: profileMessage.trailingAnchor, constant: 5).isActive = true
  267. containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
  268. containerMessage.trailingAnchor.constraint(lessThanOrEqualTo: cellMessage.contentView.trailingAnchor, constant: -80).isActive = true
  269. containerMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
  270. if (dataMessages[indexPath.row]["attachment_flag"] as? String == "11" && dataMessages[indexPath.row]["reff_id"]as? String == "") {
  271. containerMessage.backgroundColor = .clear
  272. } else {
  273. containerMessage.backgroundColor = .grayColor
  274. }
  275. containerMessage.layer.cornerRadius = 10.0
  276. containerMessage.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner]
  277. containerMessage.clipsToBounds = true
  278. timeMessage.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: 8).isActive = true
  279. let nameSender = UILabel()
  280. containerMessage.addSubview(nameSender)
  281. nameSender.translatesAutoresizingMaskIntoConstraints = false
  282. nameSender.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 15).isActive = true
  283. nameSender.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  284. nameSender.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  285. nameSender.font = UIFont.systemFont(ofSize: 12).bold
  286. nameSender.text = dataProfile["name"]
  287. nameSender.textAlignment = .left
  288. nameSender.textColor = .mainColor
  289. }
  290. if (dataMessages[indexPath.row]["is_stared"] as? String == "1") {
  291. let imageStared = UIImageView()
  292. cellMessage.contentView.addSubview(imageStared)
  293. imageStared.translatesAutoresizingMaskIntoConstraints = false
  294. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  295. imageStared.bottomAnchor.constraint(equalTo: statusMessage.topAnchor).isActive = true
  296. imageStared.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: -8).isActive = true
  297. } else {
  298. imageStared.bottomAnchor.constraint(equalTo: timeMessage.topAnchor).isActive = true
  299. imageStared.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: 8).isActive = true
  300. }
  301. imageStared.widthAnchor.constraint(equalToConstant: 15).isActive = true
  302. imageStared.heightAnchor.constraint(equalToConstant: 15).isActive = true
  303. imageStared.image = UIImage(systemName: "star.fill")
  304. imageStared.backgroundColor = .clear
  305. imageStared.tintColor = .systemYellow
  306. }
  307. messageText.numberOfLines = 0
  308. messageText.lineBreakMode = .byWordWrapping
  309. containerMessage.addSubview(messageText)
  310. topMarginText.isActive = true
  311. if dataMessages[indexPath.row]["attachment_flag"] as! String == "27" || dataMessages[indexPath.row]["attachment_flag"] as! String == "26" {
  312. messageText.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 85).isActive = true
  313. let imageLS = UIImageView()
  314. containerMessage.addSubview(imageLS)
  315. imageLS.translatesAutoresizingMaskIntoConstraints = false
  316. NSLayoutConstraint.activate([
  317. imageLS.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15.0),
  318. imageLS.trailingAnchor.constraint(equalTo: messageText.leadingAnchor, constant: -10.0),
  319. imageLS.centerYAnchor.constraint(equalTo: containerMessage.centerYAnchor),
  320. imageLS.heightAnchor.constraint(equalToConstant: 60.0)
  321. ])
  322. if dataMessages[indexPath.row]["attachment_flag"] as! String == "26" {
  323. imageLS.image = UIImage(named: "pb_seminar_wpr", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
  324. } else if dataMessages[indexPath.row]["attachment_flag"] as! String == "27" {
  325. imageLS.image = UIImage(named: "pb_live_tv", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
  326. }
  327. } else {
  328. messageText.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  329. }
  330. messageText.bottomAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: -15).isActive = true
  331. messageText.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  332. var textChat = (dataMessages[indexPath.row]["message_text"])! as? String
  333. if (dataMessages[indexPath.row]["lock"] != nil && (dataMessages[indexPath.row]["lock"])! as? String == "1") {
  334. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  335. textChat = "🚫 _"+"You were deleted this message".localized()+"_"
  336. } else {
  337. textChat = "🚫 _"+"This message was deleted".localized()+"_"
  338. }
  339. }
  340. let imageSticker = UIImageView()
  341. if let attachmentFlag = dataMessages[indexPath.row]["attachment_flag"], let attachmentFlag = attachmentFlag as? String {
  342. if attachmentFlag == "27" || attachmentFlag == "26", let data = textChat { // live streaming
  343. if let json = try! JSONSerialization.jsonObject(with: data.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  344. Database().database?.inTransaction({ fmdb, rollback in
  345. let title = json["title"] as! String
  346. let description = json["description"] as! String
  347. let start = json["time"] as! Int64
  348. let by = json["by"] as! String
  349. var type = "*Live Streaming*"
  350. if attachmentFlag == "26" {
  351. type = "*Seminar*"
  352. }
  353. if let c = Database().getRecords(fmdb: fmdb, query: "select first_name || ' ' || last_name from BUDDY where f_pin = '\(by)'"), c.next() {
  354. let name = c.string(forColumnIndex: 0)!
  355. messageText.attributedText = "\(type) \nTitle: \(title) \nDescription: \(description) \nStart: \(Date(milliseconds: start).format(dateFormat: "dd/MM/yyyy HH:mm")) \nBroadcaster: \(name)".richText()
  356. c.close()
  357. } else {
  358. messageText.attributedText = ("\(type) \nTitle: \(title) \nDescription: \(description) \nStart: \(Date(milliseconds: start).format(dateFormat: "dd/MM/yyyy HH:mm")) \nBroadcaster: " + "Unknown".localized()).richText()
  359. }
  360. })
  361. }
  362. } else if attachmentFlag == "11" {
  363. messageText.text = ""
  364. topMarginText.constant = topMarginText.constant + 100
  365. containerMessage.addSubview(imageSticker)
  366. imageSticker.translatesAutoresizingMaskIntoConstraints = false
  367. imageSticker.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 27.0).isActive = true
  368. imageSticker.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor).isActive = true
  369. imageSticker.bottomAnchor.constraint(equalTo: messageText.topAnchor, constant: -5).isActive = true
  370. imageSticker.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor).isActive = true
  371. imageSticker.widthAnchor.constraint(equalToConstant: 80).isActive = true
  372. imageSticker.image = UIImage(named: (textChat?.components(separatedBy: "/")[1])!, in: Bundle.resourceBundle(for: Nexilis.self), with: nil) //resourcesMediaBundle
  373. imageSticker.contentMode = .scaleAspectFit
  374. }
  375. else {
  376. messageText.attributedText = textChat!.richText()
  377. }
  378. } else {
  379. messageText.attributedText = textChat!.richText()
  380. }
  381. messageText.font = UIFont.systemFont(ofSize: 12)
  382. messageText.isUserInteractionEnabled = false
  383. if !textChat!.isEmpty {
  384. if textChat!.contains("■"){
  385. textChat = textChat?.components(separatedBy: "■")[0]
  386. textChat = textChat?.trimmingCharacters(in: .whitespacesAndNewlines)
  387. }
  388. let listTextEnter = textChat!.split(separator: "\n")
  389. for j in 0...listTextEnter.count - 1 {
  390. let listText = listTextEnter[j].split(separator: " ")
  391. if listText.count > 0 {
  392. for i in 0...listText.count - 1 {
  393. if listText[i].lowercased().checkStartWithLink() {
  394. let attributedString = textChat!.richText()
  395. let rangeTapLink = (attributedString.string as NSString).range(of: String(listText[i]))
  396. attributedString.addAttributes([.foregroundColor: UIColor.blue, .underlineStyle: NSUnderlineStyle.single.rawValue], range: rangeTapLink)
  397. messageText.attributedText = attributedString
  398. messageText.isUserInteractionEnabled = true
  399. let longPress = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPressLink(_:)))
  400. longPress.minimumPressDuration = 0.1
  401. containerMessage.addGestureRecognizer(longPress)
  402. }
  403. }
  404. }
  405. }
  406. }
  407. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  408. messageText.textColor = .white
  409. } else {
  410. messageText.textColor = .black
  411. }
  412. let stringDate = (dataMessages[indexPath.row]["server_date"] as! String)
  413. let date = Date(milliseconds: Int64(stringDate)!)
  414. let formatter = DateFormatter()
  415. formatter.dateFormat = "HH:mm"
  416. formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  417. timeMessage.text = formatter.string(from: date as Date)
  418. timeMessage.font = UIFont.systemFont(ofSize: 10, weight: .medium)
  419. timeMessage.textColor = .lightGray
  420. let thumbChat = dataMessages[indexPath.row]["thumb_id"] as! String
  421. let imageChat = dataMessages[indexPath.row]["image_id"] as! String
  422. let videoChat = dataMessages[indexPath.row]["video_id"] as! String
  423. let fileChat = dataMessages[indexPath.row]["file_id"] as! String
  424. let imageThumb = UIImageView()
  425. let containerViewFile = UIView()
  426. if (thumbChat != "") {
  427. topMarginText.constant = topMarginText.constant + 205
  428. containerMessage.addSubview(imageThumb)
  429. imageThumb.translatesAutoresizingMaskIntoConstraints = false
  430. imageThumb.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 32).isActive = true
  431. imageThumb.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  432. imageThumb.bottomAnchor.constraint(equalTo: messageText.topAnchor, constant: -5).isActive = true
  433. imageThumb.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  434. imageThumb.widthAnchor.constraint(equalToConstant: self.view.frame.size.width * 0.6).isActive = true
  435. imageThumb.layer.cornerRadius = 5.0
  436. imageThumb.clipsToBounds = true
  437. imageThumb.contentMode = .scaleAspectFill
  438. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  439. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  440. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  441. if let dirPath = paths.first {
  442. let thumbURL = URL(fileURLWithPath: dirPath).appendingPathComponent(thumbChat)
  443. let image = UIImage(contentsOfFile: thumbURL.path)
  444. imageThumb.image = image
  445. let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(videoChat)
  446. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(imageChat)
  447. if !FileManager.default.fileExists(atPath: imageURL.path) || !FileManager.default.fileExists(atPath: videoURL.path){
  448. let blurEffect = UIBlurEffect(style: UIBlurEffect.Style.light)
  449. let blurEffectView = UIVisualEffectView(effect: blurEffect)
  450. blurEffectView.frame = CGRect(x: 0, y: 0, width: imageThumb.frame.size.width, height: imageThumb.frame.size.height)
  451. blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  452. let imageDownload = UIImageView(image: UIImage(systemName: "arrow.down.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 50, weight: .bold, scale: .default)))
  453. imageThumb.addSubview(blurEffectView)
  454. imageThumb.addSubview(imageDownload)
  455. imageDownload.tintColor = .black.withAlphaComponent(0.3)
  456. imageDownload.translatesAutoresizingMaskIntoConstraints = false
  457. imageDownload.centerXAnchor.constraint(equalTo: imageThumb.centerXAnchor).isActive = true
  458. imageDownload.centerYAnchor.constraint(equalTo: imageThumb.centerYAnchor).isActive = true
  459. }
  460. }
  461. if (videoChat != "") {
  462. let imagePlay = UIImageView(image: UIImage(systemName: "play.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .bold, scale: .default))?.imageWithInsets(insets: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10))?.withTintColor(.white))
  463. imagePlay.circle()
  464. imageThumb.addSubview(imagePlay)
  465. imagePlay.backgroundColor = .black.withAlphaComponent(0.3)
  466. imagePlay.translatesAutoresizingMaskIntoConstraints = false
  467. imagePlay.centerXAnchor.constraint(equalTo: imageThumb.centerXAnchor).isActive = true
  468. imagePlay.centerYAnchor.constraint(equalTo: imageThumb.centerYAnchor).isActive = true
  469. }
  470. if (dataMessages[indexPath.row]["progress"] as! Double != 100.0 && dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  471. let container = UIView()
  472. imageThumb.addSubview(container)
  473. container.translatesAutoresizingMaskIntoConstraints = false
  474. container.bottomAnchor.constraint(equalTo: imageThumb.bottomAnchor, constant: -10).isActive = true
  475. container.leadingAnchor.constraint(equalTo: imageThumb.leadingAnchor, constant: 10).isActive = true
  476. container.widthAnchor.constraint(equalToConstant: 30).isActive = true
  477. container.heightAnchor.constraint(equalToConstant: 30).isActive = true
  478. let circlePath = UIBezierPath(arcCenter: CGPoint(x: 10, y: 20), radius: 15, startAngle: -(.pi / 2), endAngle: .pi * 2, clockwise: true)
  479. let trackShape = CAShapeLayer()
  480. trackShape.path = circlePath.cgPath
  481. trackShape.fillColor = UIColor.black.withAlphaComponent(0.3).cgColor
  482. trackShape.lineWidth = 3
  483. trackShape.strokeColor = UIColor.mainColor.withAlphaComponent(0.3).cgColor
  484. container.backgroundColor = .clear
  485. container.layer.addSublayer(trackShape)
  486. let shapeLoading = CAShapeLayer()
  487. shapeLoading.path = circlePath.cgPath
  488. shapeLoading.fillColor = UIColor.clear.cgColor
  489. shapeLoading.lineWidth = 3
  490. shapeLoading.strokeEnd = 0
  491. shapeLoading.strokeColor = UIColor.mainColor.cgColor
  492. container.layer.addSublayer(shapeLoading)
  493. let imageupload = UIImageView(image: UIImage(systemName: "arrow.up", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
  494. imageupload.tintColor = .white
  495. container.addSubview(imageupload)
  496. imageupload.translatesAutoresizingMaskIntoConstraints = false
  497. imageupload.bottomAnchor.constraint(equalTo: imageThumb.bottomAnchor, constant: -10).isActive = true
  498. imageupload.leadingAnchor.constraint(equalTo: imageThumb.leadingAnchor, constant: 10).isActive = true
  499. imageupload.widthAnchor.constraint(equalToConstant: 20).isActive = true
  500. imageupload.heightAnchor.constraint(equalToConstant: 20).isActive = true
  501. }
  502. let objectTap = ObjectGesture(target: self, action: #selector(contentMessageTapped(_:)))
  503. imageThumb.isUserInteractionEnabled = true
  504. imageThumb.addGestureRecognizer(objectTap)
  505. objectTap.image_id = imageChat
  506. objectTap.video_id = videoChat
  507. objectTap.imageView = imageThumb
  508. objectTap.indexPath = indexPath
  509. }
  510. if (fileChat != "") {
  511. topMarginText.constant = topMarginText.constant + 55
  512. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  513. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  514. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  515. let arrExtFile = (textChat?.components(separatedBy: "|")[0])?.split(separator: ".")
  516. let finalExtFile = arrExtFile![arrExtFile!.count - 1]
  517. if let dirPath = paths.first {
  518. let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(fileChat)
  519. if let dataFile = try? Data(contentsOf: fileURL) {
  520. var sizeOfFile = Int(dataFile.count / 1000000)
  521. if (sizeOfFile < 1) {
  522. sizeOfFile = Int(dataFile.count / 1000)
  523. if (finalExtFile.count > 4) {
  524. messageText.text = "\(sizeOfFile) kB \u{2022} TXT"
  525. }else {
  526. messageText.text = "\(sizeOfFile) kB \u{2022} \(finalExtFile.uppercased())"
  527. }
  528. } else {
  529. if (finalExtFile.count > 4) {
  530. messageText.text = "\(sizeOfFile) MB \u{2022} TXT"
  531. }else {
  532. messageText.text = "\(sizeOfFile) MB \u{2022} \(finalExtFile.uppercased())"
  533. }
  534. }
  535. } else {
  536. messageText.text = ""
  537. }
  538. }
  539. containerMessage.addSubview(containerViewFile)
  540. containerViewFile.translatesAutoresizingMaskIntoConstraints = false
  541. containerViewFile.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 32).isActive = true
  542. containerViewFile.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  543. containerViewFile.bottomAnchor.constraint(equalTo:messageText.topAnchor, constant: -5).isActive = true
  544. containerViewFile.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  545. containerViewFile.heightAnchor.constraint(equalToConstant: 50).isActive = true
  546. containerViewFile.backgroundColor = .black.withAlphaComponent(0.2)
  547. containerViewFile.layer.cornerRadius = 5.0
  548. containerViewFile.clipsToBounds = true
  549. let imageFile = UIImageView(image: UIImage(systemName: "doc.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 30, weight: .bold, scale: .default)))
  550. containerViewFile.addSubview(imageFile)
  551. let nameFile = UILabel()
  552. containerViewFile.addSubview(nameFile)
  553. imageFile.translatesAutoresizingMaskIntoConstraints = false
  554. imageFile.leadingAnchor.constraint(equalTo: containerViewFile.leadingAnchor, constant: 5).isActive = true
  555. imageFile.trailingAnchor.constraint(equalTo: nameFile.leadingAnchor, constant: -5).isActive = true
  556. imageFile.centerYAnchor.constraint(equalTo: containerViewFile.centerYAnchor).isActive = true
  557. imageFile.widthAnchor.constraint(equalToConstant: 30).isActive = true
  558. imageFile.heightAnchor.constraint(equalToConstant: 30).isActive = true
  559. imageFile.tintColor = .docColor
  560. nameFile.translatesAutoresizingMaskIntoConstraints = false
  561. nameFile.centerYAnchor.constraint(equalTo: containerViewFile.centerYAnchor).isActive = true
  562. nameFile.widthAnchor.constraint(lessThanOrEqualToConstant: 200).isActive = true
  563. nameFile.font = UIFont.systemFont(ofSize: 12, weight: .medium)
  564. nameFile.textColor = .white
  565. nameFile.text = textChat?.components(separatedBy: "|")[0]
  566. if (dataMessages[indexPath.row]["progress"] as! Double != 100.0) {
  567. let containerLoading = UIView()
  568. containerViewFile.addSubview(containerLoading)
  569. containerLoading.translatesAutoresizingMaskIntoConstraints = false
  570. containerLoading.centerYAnchor.constraint(equalTo: containerViewFile.centerYAnchor).isActive = true
  571. containerLoading.leadingAnchor.constraint(equalTo: nameFile.trailingAnchor, constant: 5).isActive = true
  572. containerLoading.trailingAnchor.constraint(equalTo: containerViewFile.trailingAnchor, constant: -5).isActive = true
  573. containerLoading.widthAnchor.constraint(equalToConstant: 30).isActive = true
  574. containerLoading.heightAnchor.constraint(equalToConstant: 30).isActive = true
  575. let circlePath = UIBezierPath(arcCenter: CGPoint(x: 15, y: 15), radius: 10, startAngle: -(.pi / 2), endAngle: .pi * 2, clockwise: true)
  576. let trackShape = CAShapeLayer()
  577. trackShape.path = circlePath.cgPath
  578. trackShape.fillColor = UIColor.clear.cgColor
  579. trackShape.lineWidth = 5
  580. trackShape.strokeColor = UIColor.mainColor.withAlphaComponent(0.3).cgColor
  581. containerLoading.layer.addSublayer(trackShape)
  582. let shapeLoading = CAShapeLayer()
  583. shapeLoading.path = circlePath.cgPath
  584. shapeLoading.fillColor = UIColor.clear.cgColor
  585. shapeLoading.lineWidth = 3
  586. shapeLoading.strokeEnd = 0
  587. shapeLoading.strokeColor = UIColor.mainColor.cgColor
  588. containerLoading.layer.addSublayer(shapeLoading)
  589. let imageupload = UIImageView(image: UIImage(systemName: "arrow.up", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
  590. imageupload.tintColor = .white
  591. containerLoading.addSubview(imageupload)
  592. imageupload.translatesAutoresizingMaskIntoConstraints = false
  593. imageupload.centerYAnchor.constraint(equalTo: containerLoading.centerYAnchor).isActive = true
  594. imageupload.centerXAnchor.constraint(equalTo: containerLoading.centerXAnchor).isActive = true
  595. } else {
  596. nameFile.trailingAnchor.constraint(equalTo: containerViewFile.trailingAnchor, constant: -5).isActive = true
  597. }
  598. let objectTap = ObjectGesture(target: self, action: #selector(contentMessageTapped(_:)))
  599. containerViewFile.addGestureRecognizer(objectTap)
  600. objectTap.containerFile = containerViewFile
  601. objectTap.labelFile = nameFile
  602. objectTap.file_id = fileChat
  603. objectTap.indexPath = indexPath
  604. }
  605. let containerLinkMessage = UIView()
  606. if thumbChat.isEmpty && fileChat.isEmpty && !textChat!.isEmpty {
  607. var text = ""
  608. let listTextSplitBreak = textChat!.components(separatedBy: "\n")
  609. let indexFirstLinkSplitBreak = listTextSplitBreak.firstIndex(where: { $0.contains("www.") || $0.contains("http://") || $0.contains("https://") })
  610. if indexFirstLinkSplitBreak != nil {
  611. let listTextSplitSpace = listTextSplitBreak[indexFirstLinkSplitBreak!].components(separatedBy: " ")
  612. 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) })
  613. if indexFirstLinkSplitSpace != nil {
  614. text = listTextSplitSpace[indexFirstLinkSplitSpace!]
  615. }
  616. }
  617. if !text.isEmpty {
  618. func showLink() {
  619. if let data = try! JSONSerialization.jsonObject(with: dataURL.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  620. let title = data["title"] as! String
  621. let description = data["description"] as! String
  622. let imageUrl = data["imageUrl"] as? String
  623. let link = data["link"] as! String
  624. topMarginText.constant = topMarginText.constant + 85
  625. containerMessage.addSubview(containerLinkMessage)
  626. containerLinkMessage.translatesAutoresizingMaskIntoConstraints = false
  627. containerLinkMessage.leadingAnchor.constraint(equalTo:containerMessage.leadingAnchor, constant: 15).isActive = true
  628. if dataMessages[indexPath.row]["attachment_flag"] as? String == "11" {
  629. containerLinkMessage.bottomAnchor.constraint(equalTo: imageSticker.topAnchor, constant: -5).isActive = true
  630. } else {
  631. containerLinkMessage.bottomAnchor.constraint(equalTo: messageText.topAnchor, constant: -5).isActive = true
  632. }
  633. containerLinkMessage.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  634. containerLinkMessage.heightAnchor.constraint(equalToConstant: 80.0).isActive = true
  635. containerLinkMessage.backgroundColor = .gray.withAlphaComponent(0.2)
  636. let imagePreview = UIImageView()
  637. if imageUrl != nil {
  638. containerLinkMessage.addSubview(imagePreview)
  639. imagePreview.translatesAutoresizingMaskIntoConstraints = false
  640. imagePreview.leadingAnchor.constraint(equalTo: containerLinkMessage.leadingAnchor).isActive = true
  641. imagePreview.bottomAnchor.constraint(equalTo: containerLinkMessage.bottomAnchor).isActive = true
  642. imagePreview.topAnchor.constraint(equalTo: containerLinkMessage.topAnchor).isActive = true
  643. imagePreview.widthAnchor.constraint(equalToConstant: 80.0).isActive = true
  644. if !imageUrl!.starts(with: "https://") {
  645. imagePreview.loadImageAsync(with: "https://www.google.be" + imageUrl!)
  646. } else {
  647. imagePreview.loadImageAsync(with: imageUrl)
  648. }
  649. imagePreview.contentMode = .scaleToFill
  650. }
  651. let titlePreview = UILabel()
  652. containerLinkMessage.addSubview(titlePreview)
  653. titlePreview.translatesAutoresizingMaskIntoConstraints = false
  654. if imageUrl != nil {
  655. titlePreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
  656. } else {
  657. titlePreview.leadingAnchor.constraint(equalTo: containerLinkMessage.leadingAnchor, constant: 5.0).isActive = true
  658. }
  659. titlePreview.topAnchor.constraint(equalTo: containerLinkMessage.topAnchor, constant: 25.0).isActive = true
  660. titlePreview.trailingAnchor.constraint(equalTo: containerLinkMessage.trailingAnchor, constant: -80.0).isActive = true
  661. titlePreview.text = title
  662. titlePreview.font = UIFont.systemFont(ofSize: 14.0, weight: .bold)
  663. titlePreview.textColor = .black
  664. let descPreview = UILabel()
  665. containerLinkMessage.addSubview(descPreview)
  666. descPreview.translatesAutoresizingMaskIntoConstraints = false
  667. if imageUrl != nil {
  668. descPreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
  669. } else {
  670. descPreview.leadingAnchor.constraint(equalTo: containerLinkMessage.leadingAnchor, constant: 5.0).isActive = true
  671. }
  672. descPreview.topAnchor.constraint(equalTo: titlePreview.bottomAnchor).isActive = true
  673. descPreview.trailingAnchor.constraint(equalTo: containerLinkMessage.trailingAnchor, constant: -80.0).isActive = true
  674. descPreview.text = description
  675. descPreview.font = UIFont.systemFont(ofSize: 12.0)
  676. descPreview.textColor = .gray
  677. descPreview.numberOfLines = 1
  678. let linkPreview = UILabel()
  679. containerLinkMessage.addSubview(linkPreview)
  680. linkPreview.translatesAutoresizingMaskIntoConstraints = false
  681. if imageUrl != nil {
  682. linkPreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
  683. } else {
  684. linkPreview.leadingAnchor.constraint(equalTo: containerLinkMessage.leadingAnchor, constant: 5.0).isActive = true
  685. }
  686. linkPreview.topAnchor.constraint(equalTo: descPreview.bottomAnchor, constant: 8.0).isActive = true
  687. linkPreview.trailingAnchor.constraint(equalTo: containerLinkMessage.trailingAnchor, constant: -80.0).isActive = true
  688. linkPreview.text = link
  689. linkPreview.font = UIFont.systemFont(ofSize: 10.0)
  690. linkPreview.textColor = .gray
  691. linkPreview.numberOfLines = 1
  692. let objectTap = ObjectGesture(target: self, action: #selector(tapMessageText(_:)))
  693. objectTap.message_id = text
  694. containerLinkMessage.addGestureRecognizer(objectTap)
  695. }
  696. }
  697. var dataURL = ""
  698. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  699. if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select data_link from LINK_PREVIEW where link='\(text)'"), cursor.next() {
  700. if let data = cursor.string(forColumnIndex: 0) {
  701. dataURL = data
  702. }
  703. cursor.close()
  704. }
  705. })
  706. if dataURL.isEmpty {
  707. let slp = SwiftLinkPreview(session: URLSession.shared,
  708. workQueue: SwiftLinkPreview.defaultWorkQueue,
  709. responseQueue: DispatchQueue.main,
  710. cache: DisabledCache.instance)
  711. let preview = slp.preview(text,
  712. onSuccess: { result in
  713. let title = result.title ?? "No Title"
  714. let description = text.contains("google.com") ? "" : result.description
  715. let imageUrl = result.image
  716. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  717. do {
  718. var dataJson: [String: Any] = [:]
  719. dataJson["title"] = title
  720. dataJson["description"] = description
  721. dataJson["imageUrl"] = imageUrl
  722. dataJson["link"] = text
  723. guard let json = String(data: try! JSONSerialization.data(withJSONObject: dataJson, options: []), encoding: String.Encoding.utf8) else {
  724. return
  725. }
  726. _ = try Database.shared.insertRecord(fmdb: fmdb, table: "LINK_PREVIEW", cvalues: [
  727. "id" : "\(Date().currentTimeMillis().toHex())",
  728. "link" : text,
  729. "data_link" : json,
  730. "retry": 0
  731. ], replace: true)
  732. dataURL = json
  733. showLink()
  734. DispatchQueue.main.async {
  735. tableView.reloadRows(at: [indexPath], with: .none)
  736. }
  737. } catch {
  738. rollback.pointee = true
  739. //print(error)
  740. }
  741. })
  742. }, onError: { error in
  743. })
  744. } else {
  745. showLink()
  746. }
  747. }
  748. }
  749. return cellMessage
  750. }
  751. @objc func handleLongPressLink(_ gestureRecognizer: UILongPressGestureRecognizer) {
  752. func showMenuContext() {
  753. if gestureRecognizer.state == .cancelled || gestureRecognizer.state == .ended{
  754. timerCheckLink?.invalidate()
  755. } else if gestureRecognizer.state == .began {
  756. timerCheckLink = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: false, block: {_ in
  757. let interaction = UIContextMenuInteraction(delegate: self)
  758. gestureRecognizer.view!.addInteraction(interaction)
  759. guard let interaction = gestureRecognizer.view!.interactions.first,
  760. let data = Data(base64Encoded: "X3ByZXNlbnRNZW51QXRMb2NhdGlvbjo="),
  761. let str = String(data: data, encoding: .utf8)
  762. else {
  763. return
  764. }
  765. let selector = NSSelectorFromString(str)
  766. guard interaction.responds(to: selector) else {
  767. return
  768. }
  769. let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
  770. impactHeavy.impactOccurred()
  771. interaction.perform(selector, with: self.view)
  772. self.showMenuContext = true
  773. })
  774. }
  775. }
  776. if gestureRecognizer.state == .began {
  777. let touchPoint = gestureRecognizer.location(in: self.view)
  778. touchedSubview = self.view.hitTest(touchPoint, with: nil) ?? UIView()
  779. if !(touchedSubview is UILabel) {
  780. showMenuContext()
  781. }
  782. }
  783. guard let label = touchedSubview as? UILabel else { return }
  784. let touchPointLabel = gestureRecognizer.location(in: label)
  785. if let text = label.text, let range = getWordRange(at: touchPointLabel, in: label) {
  786. let word = String(text[range])
  787. if word.starts(with: "www.") || word.starts(with: "https://") || word.starts(with: "http://") {
  788. if gestureRecognizer.state == .cancelled || gestureRecognizer.state == .ended {
  789. timerCheckLink?.invalidate()
  790. if label.isHighlighted {
  791. var stringURl = word
  792. if stringURl.starts(with: "www.") {
  793. stringURl = "https://" + stringURl.replacingOccurrences(of: "www.", with: "")
  794. }
  795. guard let url = URL(string: stringURl) else { return }
  796. UIApplication.shared.open(url)
  797. label.attributedText = removeHighlightedText(for: text, in: range, label: label)
  798. }
  799. } else if gestureRecognizer.state == .began {
  800. label.attributedText = highlightedText(for: text, in: range, label: label)
  801. timerCheckLink = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: {_ in
  802. UIPasteboard.general.string = word
  803. self.showToast(message: "Link Copied".localized(), font: UIFont.systemFont(ofSize: 12, weight: .medium), controller: self)
  804. label.attributedText = self.removeHighlightedText(for: text, in: range, label: label)
  805. })
  806. }
  807. } else {
  808. showMenuContext()
  809. }
  810. } else {
  811. showMenuContext()
  812. }
  813. }
  814. func getWordRange(at point: CGPoint, in label: UILabel) -> Range<String.Index>? {
  815. guard let text = label.text else { return nil }
  816. let layoutManager = NSLayoutManager()
  817. let textContainer = NSTextContainer(size: label.frame.size)
  818. let textStorage = NSTextStorage(attributedString: NSAttributedString(string: text))
  819. layoutManager.addTextContainer(textContainer)
  820. textStorage.addLayoutManager(layoutManager)
  821. textContainer.lineFragmentPadding = 0
  822. textContainer.maximumNumberOfLines = label.numberOfLines
  823. lastTouchPoint = point
  824. let characterIndex = layoutManager.characterIndex(for: point, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
  825. if characterIndex == text.count - 1 {
  826. return nil
  827. }
  828. var wordStartIndex = characterIndex
  829. while wordStartIndex > 0 && text[text.index(text.startIndex, offsetBy: wordStartIndex - 1)] != " " && text[text.index(text.startIndex, offsetBy: wordStartIndex - 1)] != "\n" {
  830. wordStartIndex -= 1
  831. }
  832. var wordEndIndex = characterIndex
  833. while wordEndIndex < text.count && text[text.index(text.startIndex, offsetBy: wordEndIndex)] != " " && text[text.index(text.startIndex, offsetBy: wordEndIndex)] != "\n" {
  834. wordEndIndex += 1
  835. }
  836. return text.index(text.startIndex, offsetBy: wordStartIndex)..<text.index(text.startIndex, offsetBy: wordEndIndex)
  837. }
  838. func highlightedText(for text: String, in range: Range<String.Index>, label: UILabel) -> NSAttributedString {
  839. let mutableAttributedString = label.attributedText!.mutableCopy() as! NSMutableAttributedString
  840. mutableAttributedString.addAttribute(.backgroundColor, value: UIColor.lightGray.withAlphaComponent(0.5), range: NSRange(range, in: text))
  841. label.isHighlighted = true
  842. return mutableAttributedString
  843. }
  844. func removeHighlightedText(for text: String, in range: Range<String.Index>, label: UILabel) -> NSAttributedString {
  845. let mutableAttributedString = label.attributedText!.mutableCopy() as! NSMutableAttributedString
  846. mutableAttributedString.removeAttribute(.backgroundColor, range: NSRange(range, in: text))
  847. label.isHighlighted = false
  848. return mutableAttributedString
  849. }
  850. @objc func tapMessageText(_ sender: ObjectGesture) {
  851. var stringURl = sender.message_id.lowercased()
  852. if stringURl.starts(with: "www.") {
  853. stringURl = "https://" + stringURl.replacingOccurrences(of: "www.", with: "")
  854. }
  855. guard let url = URL(string: stringURl) else { return }
  856. UIApplication.shared.open(url)
  857. }
  858. func getData() {
  859. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  860. if let cursorData = Database.shared.getRecords(fmdb: fmdb, query: "SELECT message_id, f_pin, l_pin, message_scope_id, server_date, status, message_text, audio_id, video_id, image_id, thumb_id, read_receipts, chat_id, file_id, attachment_flag, reff_id, lock, is_stared, blog_id FROM MESSAGE where is_stared=1 order by server_date asc") {
  861. while cursorData.next() {
  862. var row: [String: Any?] = [:]
  863. row["message_id"] = cursorData.string(forColumnIndex: 0)
  864. row["f_pin"] = cursorData.string(forColumnIndex: 1)
  865. row["l_pin"] = cursorData.string(forColumnIndex: 2)
  866. row["message_scope_id"] = cursorData.string(forColumnIndex: 3)
  867. row["server_date"] = cursorData.string(forColumnIndex: 4)
  868. row["status"] = cursorData.string(forColumnIndex: 5)
  869. row["message_text"] = cursorData.string(forColumnIndex: 6)
  870. row["audio_id"] = cursorData.string(forColumnIndex: 7)
  871. row["video_id"] = cursorData.string(forColumnIndex: 8)
  872. row["image_id"] = cursorData.string(forColumnIndex: 9)
  873. row["thumb_id"] = cursorData.string(forColumnIndex: 10)
  874. row["read_receipts"] = cursorData.int(forColumnIndex: 11)
  875. row["chat_id"] = cursorData.string(forColumnIndex: 12)
  876. row["file_id"] = cursorData.string(forColumnIndex: 13)
  877. row["attachment_flag"] = cursorData.string(forColumnIndex: 14)
  878. row["reff_id"] = cursorData.string(forColumnIndex: 15)
  879. row["lock"] = cursorData.string(forColumnIndex: 16)
  880. row["is_stared"] = cursorData.string(forColumnIndex: 17)
  881. row["blog_id"] = cursorData.string(forColumnIndex: 18)
  882. if let cursorStatus = Database.shared.getRecords(fmdb: fmdb, query: "SELECT status FROM MESSAGE_STATUS WHERE message_id='\(row["message_id"] as! String)'") {
  883. while cursorStatus.next() {
  884. row["status"] = cursorStatus.string(forColumnIndex: 0)
  885. }
  886. cursorStatus.close()
  887. }
  888. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  889. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  890. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  891. if let dirPath = paths.first {
  892. let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(row["video_id"] as! String)
  893. let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(row["file_id"] as! String)
  894. if ((row["video_id"] as! String) != "") {
  895. if FileManager.default.fileExists(atPath: videoURL.path){
  896. row["progress"] = 100.0
  897. } else {
  898. row["progress"] = 0.0
  899. }
  900. } else {
  901. if FileManager.default.fileExists(atPath: fileURL.path){
  902. row["progress"] = 100.0
  903. } else {
  904. row["progress"] = 0.0
  905. }
  906. }
  907. }
  908. row["chat_date"] = chatDate(stringDate: row["server_date"] as! String, messageId: row["message_id"] as! String)
  909. dataMessages.append(row)
  910. }
  911. cursorData.close()
  912. }
  913. })
  914. }
  915. private func chatDate(stringDate: String, messageId: String) -> String {
  916. let date = Date(milliseconds: Int64(stringDate)!)
  917. let calendar = Calendar.current
  918. if (calendar.isDateInToday(date)) {
  919. if !dataDates.contains("Today".localized()){
  920. dataDates.append("Today".localized())
  921. }
  922. return "Today".localized()
  923. } else {
  924. let startOfNow = calendar.startOfDay(for: Date())
  925. let startOfTimeStamp = calendar.startOfDay(for: date)
  926. let components = calendar.dateComponents([.day], from: startOfNow, to: startOfTimeStamp)
  927. let day = -(components.day!)
  928. if day == 1{
  929. if !dataDates.contains("Yesterday".localized()){
  930. dataDates.append("Yesterday".localized())
  931. }
  932. return "Yesterday".localized()
  933. } else if day < 7 {
  934. let formatter = DateFormatter()
  935. formatter.dateFormat = "EEEE"
  936. let lang = UserDefaults.standard.string(forKey: "i18n_language")
  937. if lang == "id" {
  938. formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  939. }
  940. if !dataDates.contains(formatter.string(from: date)){
  941. dataDates.append(formatter.string(from: date))
  942. }
  943. return formatter.string(from: date)
  944. } else {
  945. let formatter = DateFormatter()
  946. formatter.dateFormat = "EE, dd MMM"
  947. let lang = UserDefaults.standard.string(forKey: "i18n_language")
  948. if lang == "id" {
  949. formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  950. }
  951. let stringFormat = formatter.string(from: date as Date)
  952. if !dataDates.contains(stringFormat){
  953. dataDates.append(stringFormat)
  954. }
  955. return stringFormat
  956. }
  957. }
  958. }
  959. @objc func contentMessageTapped(_ sender: ObjectGesture) {
  960. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  961. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  962. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  963. if (sender.image_id != "") {
  964. if let dirPath = paths.first {
  965. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.image_id)
  966. if FileManager.default.fileExists(atPath: imageURL.path) {
  967. let image = UIImage(contentsOfFile: imageURL.path)
  968. let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
  969. previewImageVC.image = image
  970. previewImageVC.isHiddenTextField = true
  971. previewImageVC.modalPresentationStyle = .custom
  972. previewImageVC.modalTransitionStyle = .crossDissolve
  973. self.present(previewImageVC, animated: true, completion: nil)
  974. } else {
  975. for view in sender.imageView.subviews {
  976. if view is UIImageView {
  977. view.removeFromSuperview()
  978. }
  979. }
  980. let activityIndicator = UIActivityIndicatorView(style: .large)
  981. activityIndicator.color = .mainColor
  982. activityIndicator.hidesWhenStopped = true
  983. activityIndicator.center = CGPoint(x:sender.imageView.frame.width/2,
  984. y: sender.imageView.frame.height/2)
  985. activityIndicator.startAnimating()
  986. sender.imageView.addSubview(activityIndicator)
  987. Download().startHTTP(forKey: sender.image_id) { (name, progress) in
  988. guard progress == 100 else {
  989. return
  990. }
  991. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.image_id)
  992. let image = UIImage(contentsOfFile: imageURL.path)
  993. let save = UserDefaults.standard.bool(forKey: "saveToGallery")
  994. if save {
  995. UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
  996. }
  997. DispatchQueue.main.async {
  998. activityIndicator.stopAnimating()
  999. self.tableChatView.reloadData()
  1000. }
  1001. }
  1002. }
  1003. }
  1004. } else if (sender.video_id != "") {
  1005. if let dirPath = paths.first {
  1006. let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.video_id)
  1007. if FileManager.default.fileExists(atPath: videoURL.path) {
  1008. let player = AVPlayer(url: videoURL as URL)
  1009. let playerVC = AVPlayerViewController()
  1010. playerVC.modalPresentationStyle = .custom
  1011. playerVC.player = player
  1012. self.present(playerVC, animated: true, completion: nil)
  1013. } else {
  1014. for view in sender.imageView.subviews {
  1015. if view is UIImageView {
  1016. view.removeFromSuperview()
  1017. }
  1018. }
  1019. let container = UIView()
  1020. sender.imageView.addSubview(container)
  1021. container.translatesAutoresizingMaskIntoConstraints = false
  1022. container.centerXAnchor.constraint(equalTo: sender.imageView.centerXAnchor).isActive = true
  1023. container.centerYAnchor.constraint(equalTo: sender.imageView.centerYAnchor).isActive = true
  1024. container.widthAnchor.constraint(equalToConstant: 50).isActive = true
  1025. container.heightAnchor.constraint(equalToConstant: 50).isActive = true
  1026. let circlePath = UIBezierPath(arcCenter: CGPoint(x: 25, y: 25), radius: 20, startAngle: -(.pi / 2), endAngle: .pi * 2, clockwise: true)
  1027. let trackShape = CAShapeLayer()
  1028. trackShape.path = circlePath.cgPath
  1029. trackShape.fillColor = UIColor.clear.cgColor
  1030. trackShape.lineWidth = 10
  1031. trackShape.strokeColor = UIColor.mainColor.withAlphaComponent(0.3).cgColor
  1032. container.backgroundColor = .clear
  1033. container.layer.addSublayer(trackShape)
  1034. let shapeLoading = CAShapeLayer()
  1035. shapeLoading.path = circlePath.cgPath
  1036. shapeLoading.fillColor = UIColor.clear.cgColor
  1037. shapeLoading.lineWidth = 10
  1038. shapeLoading.strokeEnd = 0
  1039. shapeLoading.strokeColor = UIColor.mainColor.cgColor
  1040. container.layer.addSublayer(shapeLoading)
  1041. let imageDownload = UIImageView(image: UIImage(systemName: "arrow.down", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
  1042. imageDownload.tintColor = .white
  1043. container.addSubview(imageDownload)
  1044. imageDownload.translatesAutoresizingMaskIntoConstraints = false
  1045. imageDownload.centerXAnchor.constraint(equalTo: sender.imageView.centerXAnchor).isActive = true
  1046. imageDownload.centerYAnchor.constraint(equalTo: sender.imageView.centerYAnchor).isActive = true
  1047. imageDownload.widthAnchor.constraint(equalToConstant: 30).isActive = true
  1048. imageDownload.heightAnchor.constraint(equalToConstant: 30).isActive = true
  1049. Download().startHTTP(forKey: sender.video_id) { (name, progress) in
  1050. DispatchQueue.main.async {
  1051. guard progress == 100 else {
  1052. shapeLoading.strokeEnd = CGFloat(progress / 100)
  1053. return
  1054. }
  1055. let save = UserDefaults.standard.bool(forKey: "saveToGallery")
  1056. if save {
  1057. let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.video_id)
  1058. PHPhotoLibrary.shared().performChanges({
  1059. PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoURL)
  1060. }) { saved, error in
  1061. }
  1062. }
  1063. let idx = self.dataMessages.firstIndex(where: { $0["video_id"] as! String == sender.video_id})
  1064. if idx != nil {
  1065. self.dataMessages[idx!]["progress"] = progress
  1066. self.tableChatView.reloadRows(at: [sender.indexPath], with: .none)
  1067. }
  1068. }
  1069. }
  1070. }
  1071. }
  1072. } else if (sender.file_id != "") {
  1073. if let dirPath = paths.first {
  1074. let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.file_id)
  1075. if FileManager.default.fileExists(atPath: fileURL.path) {
  1076. self.previewItem = fileURL as NSURL
  1077. let previewController = QLPreviewController()
  1078. let rightBarButton = UIBarButtonItem()
  1079. previewController.navigationItem.rightBarButtonItem = rightBarButton
  1080. previewController.dataSource = self
  1081. previewController.modalPresentationStyle = .custom
  1082. self.present(previewController, animated: true)
  1083. } else {
  1084. for view in sender.containerFile.subviews {
  1085. if !(view is UIImageView) && !(view is UILabel) {
  1086. view.removeFromSuperview()
  1087. }
  1088. }
  1089. let containerLoading = UIView()
  1090. sender.containerFile.addSubview(containerLoading)
  1091. containerLoading.translatesAutoresizingMaskIntoConstraints = false
  1092. containerLoading.centerYAnchor.constraint(equalTo: sender.containerFile.centerYAnchor).isActive = true
  1093. containerLoading.leadingAnchor.constraint(equalTo: sender.labelFile.trailingAnchor, constant: 5).isActive = true
  1094. containerLoading.trailingAnchor.constraint(equalTo: sender.containerFile.trailingAnchor, constant: -5).isActive = true
  1095. containerLoading.widthAnchor.constraint(equalToConstant: 30).isActive = true
  1096. containerLoading.heightAnchor.constraint(equalToConstant: 30).isActive = true
  1097. let circlePath = UIBezierPath(arcCenter: CGPoint(x: 15, y: 15), radius: 10, startAngle: -(.pi / 2), endAngle: .pi * 2, clockwise: true)
  1098. let trackShape = CAShapeLayer()
  1099. trackShape.path = circlePath.cgPath
  1100. trackShape.fillColor = UIColor.clear.cgColor
  1101. trackShape.lineWidth = 5
  1102. trackShape.strokeColor = UIColor.mainColor.withAlphaComponent(0.3).cgColor
  1103. containerLoading.layer.addSublayer(trackShape)
  1104. let shapeLoading = CAShapeLayer()
  1105. shapeLoading.path = circlePath.cgPath
  1106. shapeLoading.fillColor = UIColor.clear.cgColor
  1107. shapeLoading.lineWidth = 3
  1108. shapeLoading.strokeEnd = 0
  1109. shapeLoading.strokeColor = UIColor.mainColor.cgColor
  1110. containerLoading.layer.addSublayer(shapeLoading)
  1111. let imageupload = UIImageView(image: UIImage(systemName: "arrow.down", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
  1112. imageupload.tintColor = .white
  1113. containerLoading.addSubview(imageupload)
  1114. imageupload.translatesAutoresizingMaskIntoConstraints = false
  1115. imageupload.centerYAnchor.constraint(equalTo: containerLoading.centerYAnchor).isActive = true
  1116. imageupload.centerXAnchor.constraint(equalTo: containerLoading.centerXAnchor).isActive = true
  1117. Download().startHTTP(forKey: sender.file_id) { (name, progress) in
  1118. DispatchQueue.main.async {
  1119. guard progress == 100 else {
  1120. shapeLoading.strokeEnd = CGFloat(progress / 100)
  1121. return
  1122. }
  1123. let idx = self.dataMessages.firstIndex(where: { $0["file_id"] as! String == sender.file_id})
  1124. if idx != nil {
  1125. self.dataMessages[idx!]["progress"] = progress
  1126. self.tableChatView.reloadRows(at: [sender.indexPath], with: .none)
  1127. }
  1128. }
  1129. }
  1130. }
  1131. }
  1132. } else {
  1133. DispatchQueue.main.async {
  1134. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as! String == sender.message_id})
  1135. if idx == nil {
  1136. return
  1137. }
  1138. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as! String)
  1139. if section == nil {
  1140. return
  1141. }
  1142. let row = self.dataMessages.filter({ $0["chat_date"] as! String == self.dataDates[section!]}).firstIndex(where: { $0["message_id"] as! String == self.dataMessages[idx!]["message_id"] as! String})
  1143. if row == nil {
  1144. return
  1145. }
  1146. let indexPath = IndexPath(row: row!, section: section!)
  1147. self.tableChatView.scrollToRow(at: indexPath, at: .middle, animated: true)
  1148. DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
  1149. if let cell = self.tableChatView.cellForRow(at: indexPath) {
  1150. let containerMessage = cell.contentView.subviews[0]
  1151. let idMe = UserDefaults.standard.string(forKey: "me") as String?
  1152. if (self.dataMessages[idx!]["f_pin"] as? String == idMe) {
  1153. containerMessage.backgroundColor = .mainColor.withAlphaComponent(0.3)
  1154. DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
  1155. if (self.dataMessages[idx!]["attachment_flag"] as? String == "11") {
  1156. containerMessage.backgroundColor = .clear
  1157. } else {
  1158. containerMessage.backgroundColor = .mainColor
  1159. }
  1160. }
  1161. } else {
  1162. containerMessage.backgroundColor = .grayColor.withAlphaComponent(0.3)
  1163. DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
  1164. if (self.dataMessages[idx!]["attachment_flag"] as? String == "11") {
  1165. containerMessage.backgroundColor = .clear
  1166. } else {
  1167. containerMessage.backgroundColor = .grayColor
  1168. }
  1169. }
  1170. }
  1171. }
  1172. }
  1173. }
  1174. }
  1175. }
  1176. func getDataProfile(f_pin: String) -> [String: String]{
  1177. var data: [String: String] = [:]
  1178. Database().database?.inTransaction({ fmdb, rollback in
  1179. if let c = Database().getRecords(fmdb: fmdb, query: "select first_name || ' ' || last_name, image_id from BUDDY where f_pin = '\(f_pin)'"), c.next() {
  1180. data["name"] = c.string(forColumnIndex: 0)!.trimmingCharacters(in: .whitespacesAndNewlines)
  1181. data["image_id"] = c.string(forColumnIndex: 1)!
  1182. c.close()
  1183. }
  1184. else if f_pin == "-999" {
  1185. data["name"] = "Bot".localized()
  1186. data["image_id"] = "pb_powered"
  1187. }
  1188. else {
  1189. data["name"] = "Unknown".localized()
  1190. data["image_id"] = ""
  1191. }
  1192. })
  1193. return data
  1194. }
  1195. private func getDataProfileFromMessageId(message_id: String) -> [String: String]{
  1196. var data: [String: String] = [:]
  1197. Database().database?.inTransaction({ fmdb, rollback in
  1198. if let c = Database().getRecords(fmdb: fmdb, query: "select f_display_name from MESSAGE where message_id = '\(message_id)'"), c.next() {
  1199. data["name"] = c.string(forColumnIndex: 0)!
  1200. c.close()
  1201. } else {
  1202. data["name"] = "Unknown".localized()
  1203. data["image_id"] = ""
  1204. }
  1205. })
  1206. return data
  1207. }
  1208. public func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willEndFor configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionAnimating?) {
  1209. if showMenuContext {
  1210. showMenuContext = false
  1211. interaction.view!.removeInteraction(interaction)
  1212. }
  1213. }
  1214. public func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
  1215. let indexPath = self.tableChatView.indexPathForRow(at: interaction.view!.convert(location, to: self.tableChatView))
  1216. let dataMessages = self.dataMessages.filter({ $0["chat_date"] as! String == dataDates[indexPath!.section]})
  1217. let star = UIAction(title: "Unstar".localized(), image: UIImage(systemName: "star.slash.fill"), handler: {(_) in
  1218. DispatchQueue.global().async {
  1219. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  1220. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  1221. "is_stared" : 0
  1222. ], _where: "message_id = '\(dataMessages[indexPath!.row]["message_id"] as! String)'")
  1223. })
  1224. }
  1225. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as! String == dataMessages[indexPath!.row]["message_id"] as! String})
  1226. if idx != nil{
  1227. self.dataMessages[idx!]["is_stared"] = "0"
  1228. }
  1229. self.dataMessages.remove(at: idx!)
  1230. self.tableChatView.deleteRows(at: [indexPath!], with: .fade)
  1231. if self.dataMessages.filter({ $0["chat_date"] as! String == dataMessages[indexPath!.row]["chat_date"] as! String }).count == 0 {
  1232. self.dataDates.remove(at: indexPath!.section)
  1233. self.tableChatView.deleteSections(IndexSet(integer: indexPath!.section), with: .fade)
  1234. }
  1235. })
  1236. let forward = UIAction(title: "Forward".localized(), image: UIImage(systemName: "arrowshape.turn.up.right.fill"), handler: {(_) in
  1237. let navigationController = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "contactChatNav") as! UINavigationController
  1238. navigationController.modalPresentationStyle = .custom
  1239. navigationController.navigationBar.tintColor = .white
  1240. navigationController.navigationBar.barTintColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  1241. navigationController.navigationBar.isTranslucent = false
  1242. navigationController.navigationBar.overrideUserInterfaceStyle = .dark
  1243. navigationController.navigationBar.barStyle = .black
  1244. let cancelButtonAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
  1245. UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes, for: .normal)
  1246. let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
  1247. navigationController.navigationBar.titleTextAttributes = textAttributes
  1248. navigationController.view.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .black : .white
  1249. if let controller = navigationController.viewControllers.first as? ContactChatViewController {
  1250. controller.isChooser = { [weak self] scope, pin in
  1251. if scope == "3" {
  1252. let editorPersonalVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorPersonalVC") as! EditorPersonal
  1253. editorPersonalVC.unique_l_pin = pin
  1254. editorPersonalVC.dataMessageForward = [dataMessages[indexPath!.row]]
  1255. self?.navigationController?.replaceAllViewController(with: editorPersonalVC, animated: true)
  1256. } else {
  1257. let editorGroupVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorGroupVC") as! EditorGroup
  1258. editorGroupVC.unique_l_pin = pin
  1259. editorGroupVC.dataMessageForward = [dataMessages[indexPath!.row]]
  1260. self?.navigationController?.replaceAllViewController(with: editorGroupVC, animated: true)
  1261. }
  1262. }
  1263. }
  1264. self.present(navigationController, animated: true, completion: nil)
  1265. })
  1266. let copy = UIAction(title: "Copy".localized(), image: UIImage(systemName: "doc.on.doc.fill"), handler: {(_) in
  1267. if (dataMessages[indexPath!.row]["attachment_flag"] as! String == "0") {
  1268. DispatchQueue.main.async {
  1269. var text = ""
  1270. let stringDate = (dataMessages[indexPath!.row]["server_date"] as! String)
  1271. let date = Date(milliseconds: Int64(stringDate)!)
  1272. let formatterDate = DateFormatter()
  1273. let formatterTime = DateFormatter()
  1274. formatterDate.dateFormat = "dd/MM/yy"
  1275. formatterDate.locale = NSLocale(localeIdentifier: "id") as Locale?
  1276. formatterTime.dateFormat = "HH:mm"
  1277. formatterTime.locale = NSLocale(localeIdentifier: "id") as Locale?
  1278. let dataProfile = self.getDataProfileFromMessageId(message_id: dataMessages[indexPath!.row]["message_id"] as! String)
  1279. if text.isEmpty {
  1280. text = "*[\(formatterDate.string(from: date as Date)) \(formatterTime.string(from: date as Date))] \(dataProfile["name"]!):*\n\(dataMessages[indexPath!.row]["message_text"] as! String)"
  1281. } else {
  1282. text = text + "\n\n*[\(formatterDate.string(from: date as Date)) \(formatterTime.string(from: date as Date))] \(dataProfile["name"]!):*\n\(dataMessages[indexPath!.row]["message_text"] as! String)"
  1283. }
  1284. text = text + "\n\n\nchat " + "Powered by Nexilis".localized()
  1285. DispatchQueue.main.async {
  1286. UIPasteboard.general.string = text
  1287. self.showToast(message: "Text coppied to clipboard".localized(), font: UIFont.systemFont(ofSize: 12, weight: .medium), controller: self)
  1288. }
  1289. }
  1290. } else {
  1291. DispatchQueue.main.async {
  1292. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  1293. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  1294. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  1295. if let dirPath = paths.first {
  1296. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(dataMessages[indexPath!.row]["image_id"] as! String)
  1297. if FileManager.default.fileExists(atPath: imageURL.path) {
  1298. let image = UIImage(contentsOfFile: imageURL.path)
  1299. UIPasteboard.general.image = image
  1300. self.showToast(message: "Image coppied to clipboard".localized(), font: UIFont.systemFont(ofSize: 12, weight: .medium), controller: self)
  1301. }
  1302. }
  1303. }
  1304. }
  1305. })
  1306. var children: [UIMenuElement] = [star, forward, copy]
  1307. // let copyOption = self.copyOption(indexPath: indexPath!)
  1308. if self.dataMessages[indexPath!.row]["f_pin"] as! String == "-999" {
  1309. children = [star]
  1310. } else if !(dataMessages[indexPath!.row]["image_id"] as! String).isEmpty || !(dataMessages[indexPath!.row]["video_id"] as! String).isEmpty || !(dataMessages[indexPath!.row]["file_id"] as! String).isEmpty || dataMessages[indexPath!.row]["attachment_flag"] as! String == "11" {
  1311. children = [star, forward]
  1312. }
  1313. return UIContextMenuConfiguration(identifier: nil,
  1314. previewProvider: nil) { _ in
  1315. UIMenu(title: "", children: children)
  1316. }
  1317. }
  1318. private func copyOption(indexPath: IndexPath) -> UIMenu {
  1319. let ratingButtonTitles = ["Text".localized(), "Image".localized()]
  1320. let dataMessages = self.dataMessages.filter({ $0["chat_date"] as! String == dataDates[indexPath.section]})
  1321. let copyActions = ratingButtonTitles
  1322. .enumerated()
  1323. .map { index, title in
  1324. return UIAction(
  1325. title: title,
  1326. identifier: nil,
  1327. handler: {(_) in if (index == 0) {
  1328. DispatchQueue.main.async {
  1329. UIPasteboard.general.string = dataMessages[indexPath.row]["message_text"] as? String
  1330. self.showToast(message: "Text coppied to clipboard".localized(), font: UIFont.systemFont(ofSize: 12, weight: .medium), controller: self)
  1331. }
  1332. } else {
  1333. DispatchQueue.main.async {
  1334. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  1335. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  1336. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  1337. if let dirPath = paths.first {
  1338. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(dataMessages[indexPath.row]["image_id"] as! String)
  1339. if FileManager.default.fileExists(atPath: imageURL.path) {
  1340. let image = UIImage(contentsOfFile: imageURL.path)
  1341. UIPasteboard.general.image = image
  1342. self.showToast(message: "Image coppied to clipboard".localized(), font: UIFont.systemFont(ofSize: 12, weight: .medium), controller: self)
  1343. }
  1344. }
  1345. }
  1346. }})
  1347. }
  1348. return UIMenu(
  1349. title: "Copy".localized(),
  1350. image: UIImage(systemName: "doc.on.doc.fill"),
  1351. children: copyActions)
  1352. }
  1353. @objc private func cancelDocumentPreview(sender: navigationQLPreviewDocument) {
  1354. sender.navigation.dismiss(animated: true, completion: nil)
  1355. }
  1356. @objc func segmentedControlValueChanged(_ sender: segmentedControllerObject) {
  1357. switch sender.selectedSegmentIndex {
  1358. case 0:
  1359. sender.navigation.viewControllers[0].children[1].view.isHidden = true
  1360. break;
  1361. case 1:
  1362. sender.navigation.viewControllers[0].children[1].view.isHidden = false
  1363. break;
  1364. default:
  1365. break;
  1366. }
  1367. }
  1368. public func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
  1369. 1
  1370. }
  1371. public func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
  1372. return self.previewItem as QLPreviewItem
  1373. }
  1374. public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  1375. let message = dataMessages[indexPath.row]
  1376. if let attachmentFlag = message["attachment_flag"], let attachmentFlag = attachmentFlag as? String {
  1377. if attachmentFlag == "27" {
  1378. let streamingController = QmeraCreateStreamingViewController()
  1379. streamingController.isJoin = true
  1380. if let messageText = message["message_text"],
  1381. let messageText = messageText as? String,
  1382. var json = try! JSONSerialization.jsonObject(with: messageText.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  1383. if json["blog"] == nil {
  1384. json["blog"] = message["blog_id"] ?? nil
  1385. }
  1386. streamingController.data = json
  1387. }
  1388. let streamingNav = UINavigationController(rootViewController: streamingController)
  1389. streamingNav.modalPresentationStyle = .custom
  1390. streamingNav.navigationBar.barTintColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  1391. streamingNav.navigationBar.tintColor = .white
  1392. let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
  1393. streamingNav.navigationBar.titleTextAttributes = textAttributes
  1394. streamingNav.navigationBar.isTranslucent = false
  1395. navigationController?.present(streamingNav, animated: true, completion: nil)
  1396. }
  1397. }
  1398. }
  1399. }