// // MessageCell.swift // Juick // // Created by Vitaly Takmazov on 13.05.2023. // Copyright © 2023 com.juick. All rights reserved. // import UIKit @objc protocol MessageCellDelegate { @objc func avatarClicked(_ uname: String) @objc func linkClicked(_ url: String) } @objc class MessageCell: UITableViewCell, UITextViewDelegate { @IBOutlet weak var avatar: UIImageView! @IBOutlet weak var message: UITextView! @IBOutlet weak var title: UILabel! @IBOutlet weak var timestamp: UILabel! @IBOutlet weak var attach: UIImageView! @IBOutlet weak var attachmentHeight: NSLayoutConstraint! @IBOutlet weak var attachmentWidth: NSLayoutConstraint! @IBOutlet weak var summary: UILabel! @objc var delegate: MessageCellDelegate? var _quoteStyle: NSMutableParagraphStyle? var _boldFont: UIFont? var _italicFont: UIFont? var attachment: String? @objc func configure(msg: Message, selectable: Bool) { updateAvatar(url: msg.user.avatar) if (msg.user.uid == nil) { AppDelegate.shared().api.getUserByUri(msg.user.uri) { user in self.title.text = user?.uname if let url = user?.avatar ?? API.defaultAvatarUrl() { self.updateAvatar(url: url) } } } if (msg.attach != nil) { self.attachment = msg.attachment.url let imageHeight = msg.attachment.medium.height as! CGFloat / UIScreen.main.scale let imageWidth = msg.attachment.medium.width as! CGFloat / UIScreen.main.scale self.attach.image = UIImage.placeholderImage(with: UIColor(named: "Muted"), size: CGSizeMake(imageWidth, imageHeight)) self.attachmentHeight.constant = imageHeight self.attachmentWidth.constant = imageWidth AppDelegate.shared().api.fetchImage(with: URL(string: msg.attachment.medium.url)) { data in if let imageData = data { UIView.transition(with: self.attach, duration: 0.3, options: .transitionCrossDissolve, animations: { self.attach.image = UIImage(data: imageData) }) } } let attachmentTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(linkClicked(_:))) attachmentTapGestureRecognizer.isEnabled = true self.attach.addGestureRecognizer(attachmentTapGestureRecognizer) self.attach.isUserInteractionEnabled = true } else { self.attachmentHeight.constant = 0 self.attach.image = nil } if (msg.user.premium) { self.title.text = "\(msg.user.uname!) ☆" } else { self.title.text = msg.user.uname! } self.timestamp.text = (AppDelegate.shared().sharedDateFormatter.date(from: msg.timestamp)! as NSDate).timeAgo() let count = msg.repliesCount?.uintValue ?? 0 if (count > 0) { if (msg.repliesBy != nil) { self.summary.text = "\(msg.repliesCount!) replies by \(msg.repliesBy!)" } else { self.summary.text = "\(msg.repliesCount!) replies" } } else { self.summary.text = nil } if (msg.unread) { self.summary.text = "\(unreadMarker) \(self.summary.text!)" self.summary.textColor = UIColor(named: "Funny") } else { self.summary.textColor = UIColor(named: "Muted") } self.message.attributedText = nil if (msg.text != nil) { self.message.delegate = self self.message.isHidden = false self.message.isSelectable = selectable self.message.isUserInteractionEnabled = selectable let txt = NSMutableAttributedString(string: msg.text, attributes: [ NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .body), NSAttributedString.Key.foregroundColor: UIColor(named: "Text")! ]) txt.beginEditing() for entity in msg.entities { let start = entity.start?.intValue ?? 0 let end = entity.end?.intValue ?? 0 let text = entity.text ?? "" let currentRange = NSMakeRange(start, end - start) txt.addAttribute(NSAttributedString.Key(rawValue: "displayText"), value: text, range: currentRange) if entity.type == "a" { txt.addAttribute(NSAttributedString.Key.link, value:entity.link!, range:currentRange) } if entity.type == "q" { txt.addAttribute(NSAttributedString.Key.foregroundColor, value:UIColor(named: "Muted")!, range:currentRange) txt.addAttribute(NSAttributedString.Key.paragraphStyle, value:_quoteStyle!, range:currentRange) } if entity.type == "u" { txt.addAttribute(NSAttributedString.Key.underlineStyle, value:NSUnderlineStyle.single, range:currentRange) } if entity.type == "b" { txt.addAttribute(NSAttributedString.Key.font, value:_boldFont!, range:currentRange) } if entity.type == "i" { txt.addAttribute(NSAttributedString.Key.font, value:_italicFont!, range:currentRange) } } txt.enumerateAttribute(NSAttributedString.Key(rawValue: "displayText"), in: NSMakeRange(0, txt.length)) { value, range, stop in if let text = value as? String { txt.replaceCharacters(in: range, with:text) } } if msg.tags != nil { let tagsList = "\(msg.tags.joined(separator: ", "))\n" txt.insert(NSAttributedString(string: tagsList, attributes: [ NSAttributedString.Key.font: _italicFont!, NSAttributedString.Key.foregroundColor: UIColor(named: "Muted")! ]), at: 0) } txt.endEditing() self.message.attributedText = txt } else { self.message.isHidden = true } } let unreadMarker = "●" func updateAvatar(url: String) { self.avatar.image = nil AppDelegate.shared().api.fetchImage(with: URL(string: url)) { data in if let imageData = data { UIView.transition(with: self.avatar, duration: 0.3, options: .transitionCrossDissolve, animations: { self.avatar.image = UIImage(data: imageData) }) } } } @objc func avatarClicked(_ sender: UITapGestureRecognizer) { if let name = self.title.text { self.delegate?.avatarClicked(name) } } func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool { self.delegate?.linkClicked(URL.absoluteString) return false } @objc func linkClicked(_ sender: UITapGestureRecognizer){ self.delegate?.linkClicked(self.attachment!) } override func awakeFromNib() { let avatarTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(avatarClicked(_:))) avatarTapRecognizer.isEnabled = true self.avatar.addGestureRecognizer(avatarTapRecognizer) self.avatar.isUserInteractionEnabled = true _quoteStyle = NSParagraphStyle.default.mutableCopy() as? NSMutableParagraphStyle _quoteStyle?.firstLineHeadIndent = 12.0 _quoteStyle?.headIndent = 12.0 _quoteStyle?.paragraphSpacing = 6.0 let fontDescriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body) guard let boldFontDescriptor = fontDescriptor.withSymbolicTraits(.traitBold) else { fatalError("Font error") } _boldFont = UIFont(descriptor: boldFontDescriptor, size: 0.0) guard let italicFontDescriptor = fontDescriptor.withSymbolicTraits(.traitItalic) else { fatalError("Font error") } _italicFont = UIFont(descriptor: italicFontDescriptor, size: 0.0) } }