|  |  |  | // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import UIKit | 
					
						
							|  |  |  | import SessionUIKit | 
					
						
							|  |  |  | import SessionUtilitiesKit | 
					
						
							|  |  |  | import SignalUtilitiesKit | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | final class MentionSelectionView: UIView, UITableViewDataSource, UITableViewDelegate { | 
					
						
							|  |  |  |     var candidates: [MentionInfo] = [] { | 
					
						
							|  |  |  |         didSet { | 
					
						
							|  |  |  |             tableView.isScrollEnabled = (candidates.count > 4) | 
					
						
							|  |  |  |             tableView.reloadData() | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     weak var delegate: MentionSelectionViewDelegate? | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     var contentOffset: CGPoint { | 
					
						
							|  |  |  |         get { tableView.contentOffset } | 
					
						
							|  |  |  |         set { tableView.contentOffset = newValue } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // MARK: - Components | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     private lazy var tableView: UITableView = { | 
					
						
							|  |  |  |         let result: UITableView = UITableView() | 
					
						
							|  |  |  |         result.accessibilityLabel = "Contact" | 
					
						
							|  |  |  |         result.dataSource = self | 
					
						
							|  |  |  |         result.delegate = self | 
					
						
							|  |  |  |         result.separatorStyle = .none | 
					
						
							|  |  |  |         result.themeBackgroundColor = .clear | 
					
						
							|  |  |  |         result.showsVerticalScrollIndicator = false | 
					
						
							|  |  |  |         result.register(view: Cell.self) | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         return result | 
					
						
							|  |  |  |     }() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // MARK: - Initialization | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     override init(frame: CGRect) { | 
					
						
							|  |  |  |         super.init(frame: frame) | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         setUpViewHierarchy() | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     required init?(coder: NSCoder) { | 
					
						
							|  |  |  |         super.init(coder: coder) | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         setUpViewHierarchy() | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private func setUpViewHierarchy() { | 
					
						
							|  |  |  |         // Table view | 
					
						
							|  |  |  |         addSubview(tableView) | 
					
						
							|  |  |  |         tableView.pin(to: self) | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Top separator | 
					
						
							|  |  |  |         let topSeparator: UIView = UIView() | 
					
						
							|  |  |  |         topSeparator.themeBackgroundColor = .borderSeparator | 
					
						
							|  |  |  |         topSeparator.set(.height, to: Values.separatorThickness) | 
					
						
							|  |  |  |         addSubview(topSeparator) | 
					
						
							|  |  |  |         topSeparator.pin(.leading, to: .leading, of: self) | 
					
						
							|  |  |  |         topSeparator.pin(.top, to: .top, of: self) | 
					
						
							|  |  |  |         topSeparator.pin(.trailing, to: .trailing, of: self) | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Bottom separator | 
					
						
							|  |  |  |         let bottomSeparator: UIView = UIView() | 
					
						
							|  |  |  |         bottomSeparator.themeBackgroundColor = .borderSeparator | 
					
						
							|  |  |  |         bottomSeparator.set(.height, to: Values.separatorThickness) | 
					
						
							|  |  |  |         addSubview(bottomSeparator) | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         bottomSeparator.pin(.leading, to: .leading, of: self) | 
					
						
							|  |  |  |         bottomSeparator.pin(.trailing, to: .trailing, of: self) | 
					
						
							|  |  |  |         bottomSeparator.pin(.bottom, to: .bottom, of: self) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // MARK: - Data | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { | 
					
						
							|  |  |  |         return candidates.count | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { | 
					
						
							|  |  |  |         let cell: Cell = tableView.dequeue(type: Cell.self, for: indexPath) | 
					
						
							|  |  |  |         cell.update( | 
					
						
							|  |  |  |             with: candidates[indexPath.row].profile, | 
					
						
							|  |  |  |             threadVariant: candidates[indexPath.row].threadVariant, | 
					
						
							|  |  |  |             isUserModeratorOrAdmin: OpenGroupManager.isUserModeratorOrAdmin( | 
					
						
							|  |  |  |                 candidates[indexPath.row].profile.id, | 
					
						
							|  |  |  |                 for: candidates[indexPath.row].openGroupRoomToken, | 
					
						
							|  |  |  |                 on: candidates[indexPath.row].openGroupServer | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |             isLast: (indexPath.row == (candidates.count - 1)) | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         return cell | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // MARK: - Interaction | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { | 
					
						
							|  |  |  |         let mentionCandidate = candidates[indexPath.row] | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         delegate?.handleMentionSelected(mentionCandidate, from: self) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // MARK: - Cell | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | private extension MentionSelectionView { | 
					
						
							|  |  |  |     final class Cell: UITableViewCell { | 
					
						
							|  |  |  |         // MARK: - UI | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         private lazy var profilePictureView: ProfilePictureView = ProfilePictureView() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         private lazy var moderatorIconImageView: UIImageView = UIImageView(image: #imageLiteral(resourceName: "Crown")) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         private lazy var displayNameLabel: UILabel = { | 
					
						
							|  |  |  |             let result: UILabel = UILabel() | 
					
						
							|  |  |  |             result.font = .systemFont(ofSize: Values.smallFontSize) | 
					
						
							|  |  |  |             result.themeTextColor = .textPrimary | 
					
						
							|  |  |  |             result.lineBreakMode = .byTruncatingTail | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             return result | 
					
						
							|  |  |  |         }() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         lazy var separator: UIView = { | 
					
						
							|  |  |  |             let result: UIView = UIView() | 
					
						
							|  |  |  |             result.themeBackgroundColor = .borderSeparator | 
					
						
							|  |  |  |             result.set(.height, to: Values.separatorThickness) | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             return result | 
					
						
							|  |  |  |         }() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // MARK: - Initialization | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { | 
					
						
							|  |  |  |             super.init(style: style, reuseIdentifier: reuseIdentifier) | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             setUpViewHierarchy() | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         required init?(coder: NSCoder) { | 
					
						
							|  |  |  |             super.init(coder: coder) | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             setUpViewHierarchy() | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         private func setUpViewHierarchy() { | 
					
						
							|  |  |  |             // Cell background color | 
					
						
							|  |  |  |             themeBackgroundColor = .settings_tabBackground | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Highlight color | 
					
						
							|  |  |  |             let selectedBackgroundView = UIView() | 
					
						
							|  |  |  |             selectedBackgroundView.themeBackgroundColor = .highlighted(.settings_tabBackground) | 
					
						
							|  |  |  |             self.selectedBackgroundView = selectedBackgroundView | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Profile picture image view | 
					
						
							|  |  |  |             let profilePictureViewSize = Values.smallProfilePictureSize | 
					
						
							|  |  |  |             profilePictureView.set(.width, to: profilePictureViewSize) | 
					
						
							|  |  |  |             profilePictureView.set(.height, to: profilePictureViewSize) | 
					
						
							|  |  |  |             profilePictureView.size = profilePictureViewSize | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Main stack view | 
					
						
							|  |  |  |             let mainStackView = UIStackView(arrangedSubviews: [ profilePictureView, displayNameLabel ]) | 
					
						
							|  |  |  |             mainStackView.axis = .horizontal | 
					
						
							|  |  |  |             mainStackView.alignment = .center | 
					
						
							|  |  |  |             mainStackView.spacing = Values.mediumSpacing | 
					
						
							|  |  |  |             mainStackView.set(.height, to: profilePictureViewSize) | 
					
						
							|  |  |  |             contentView.addSubview(mainStackView) | 
					
						
							|  |  |  |             mainStackView.pin(.leading, to: .leading, of: contentView, withInset: Values.mediumSpacing) | 
					
						
							|  |  |  |             mainStackView.pin(.top, to: .top, of: contentView, withInset: Values.smallSpacing) | 
					
						
							|  |  |  |             contentView.pin(.trailing, to: .trailing, of: mainStackView, withInset: Values.mediumSpacing) | 
					
						
							|  |  |  |             contentView.pin(.bottom, to: .bottom, of: mainStackView, withInset: Values.smallSpacing) | 
					
						
							|  |  |  |             mainStackView.set(.width, to: UIScreen.main.bounds.width - 2 * Values.mediumSpacing) | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Moderator icon image view | 
					
						
							|  |  |  |             moderatorIconImageView.set(.width, to: 20) | 
					
						
							|  |  |  |             moderatorIconImageView.set(.height, to: 20) | 
					
						
							|  |  |  |             contentView.addSubview(moderatorIconImageView) | 
					
						
							|  |  |  |             moderatorIconImageView.pin(.trailing, to: .trailing, of: profilePictureView, withInset: 1) | 
					
						
							|  |  |  |             moderatorIconImageView.pin(.bottom, to: .bottom, of: profilePictureView, withInset: 4.5) | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Separator | 
					
						
							|  |  |  |             addSubview(separator) | 
					
						
							|  |  |  |             separator.pin(.leading, to: .leading, of: self) | 
					
						
							|  |  |  |             separator.pin(.trailing, to: .trailing, of: self) | 
					
						
							|  |  |  |             separator.pin(.bottom, to: .bottom, of: self) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // MARK: - Updating | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         fileprivate func update( | 
					
						
							|  |  |  |             with profile: Profile, | 
					
						
							|  |  |  |             threadVariant: SessionThread.Variant, | 
					
						
							|  |  |  |             isUserModeratorOrAdmin: Bool, | 
					
						
							|  |  |  |             isLast: Bool | 
					
						
							|  |  |  |         ) { | 
					
						
							|  |  |  |             displayNameLabel.text = profile.displayName(for: threadVariant) | 
					
						
							|  |  |  |             profilePictureView.update( | 
					
						
							|  |  |  |                 publicKey: profile.id, | 
					
						
							|  |  |  |                 profile: profile, | 
					
						
							|  |  |  |                 threadVariant: threadVariant | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |             moderatorIconImageView.isHidden = !isUserModeratorOrAdmin | 
					
						
							|  |  |  |             separator.isHidden = isLast | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // MARK: - Delegate | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | protocol MentionSelectionViewDelegate: AnyObject { | 
					
						
							|  |  |  |     func handleMentionSelected(_ mention: MentionInfo, from view: MentionSelectionView) | 
					
						
							|  |  |  | } |