// // SelectedMembersViewController.swift // TUIRoomKit // // Created by CY zhao on 2024/6/24. // import Foundation import RTCRoomEngine protocol SelectedMemberCellProtocol: AnyObject { func didDeleteButtonClicked(in memberCell: SelectedMemberCell) } class SelectedMembersViewController: UIViewController { private(set) var showDeleteButton: Bool = true var selectedMember: [UserInfo] = [] var didDeselectMember: ((UserInfo) -> Void)? private let arrowViewHeight: CGFloat = 35.0 init(showDeleteButton: Bool = true, selectedMembers: [UserInfo] = []) { self.showDeleteButton = showDeleteButton self.selectedMember = selectedMembers super.init(nibName: nil, bundle: nil) modalPresentationStyle = .custom } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private let tableView: UITableView = { let tableView = UITableView(frame: .zero, style: .plain) tableView.backgroundColor = .clear tableView.register(SelectedMemberCell.self, forCellReuseIdentifier: SelectedMemberCell.reuseIdentifier) if #available(iOS 15.0, *) { tableView.sectionHeaderTopPadding = 0 } return tableView }() private let dropArrowView : UIView = { let view = UIView() view.backgroundColor = .white return view }() private let dropArrowImageView: UIImageView = { let view = UIImageView() view.image = UIImage(named: "room_drop_arrow", in:tuiRoomKitBundle(), compatibleWith: nil) return view }() let contentView: UIView = { let view = UIView(frame: .zero) view.backgroundColor = .white view.layer.cornerRadius = 8 view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] view.clipsToBounds = true return view }() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = UIColor.tui_color(withHex: "0F1014", alpha: 0.6) constructViewHierarchy() activateConstraints() bindInteraction() } private func constructViewHierarchy() { view.addSubview(contentView) dropArrowView.addSubview(dropArrowImageView) contentView.addSubview(dropArrowView) contentView.addSubview(tableView) } private func activateConstraints() { contentView.snp.makeConstraints { make in make.height.equalTo(610) make.leading.bottom.trailing.equalToSuperview() } dropArrowView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() make.height.equalTo(arrowViewHeight) } dropArrowImageView.snp.makeConstraints { make in make.centerX.centerY.equalToSuperview() make.width.equalTo(24.scale375()) make.height.equalTo(3.scale375()) } tableView.snp.makeConstraints { make in make.top.equalTo(dropArrowView.snp.bottom) make.leading.bottom.trailing.equalToSuperview() } } private func bindInteraction() { tableView.delegate = self tableView.dataSource = self let dropArrowTap = UITapGestureRecognizer(target: self, action: #selector(dropDownPopUpViewAction(sender:))) dropArrowView.addGestureRecognizer(dropArrowTap) dropArrowView.isUserInteractionEnabled = true } @objc func dropDownPopUpViewAction(sender: UIView) { self.dismiss(animated: true) } } extension SelectedMembersViewController: SelectedMemberCellProtocol{ func didDeleteButtonClicked(in memberCell: SelectedMemberCell) { guard let indexPath = self.tableView.indexPath(for: memberCell) else { return } let member = selectedMember[indexPath.row] self.didDeselectMember?(member) selectedMember.remove(at: indexPath.row) self.tableView.deleteRows(at: [indexPath], with: .none) guard let headerView = tableView.headerView(forSection: 0) as? SelectedMemberHeaderView else { return } headerView.updateLabel(with: selectedMember.count) } } extension SelectedMembersViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 52 } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let headerView = SelectedMemberHeaderView(reuseIdentifier: "CustomHeaderView") headerView.updateLabel(with: selectedMember.count) return headerView } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 38 } } extension SelectedMembersViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return selectedMember.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: SelectedMemberCell.reuseIdentifier, for: indexPath) as? SelectedMemberCell else { return UITableViewCell() } let member = selectedMember[indexPath.row] cell.updateView(with: member) cell.delegate = self if showDeleteButton { cell.showDeleteButton() } else { cell.hideDeleteButton() } return cell } } class SelectedMemberCell: UITableViewCell { static let reuseIdentifier = "SelectedMemberCell" weak var delegate: SelectedMemberCellProtocol? private let avatarImageView: UIImageView = { let imgView = UIImageView() imgView.layer.cornerRadius = 2 imgView.layer.masksToBounds = true return imgView }() private let nameLabel: UILabel = { let label = UILabel() label.textColor = UIColor.tui_color(withHex: "22262E") label.textAlignment = isRTL ? .right : .left label.font = UIFont(name: "PingFangSC-Regular", size: 14) label.numberOfLines = 1 return label }() let deleteButton: UIButton = { let button = LargerHitAreaButton() let image = UIImage(named: "room_delete", in: tuiRoomKitBundle(), compatibleWith: nil) button.setImage(image, for: .normal) return button }() private var isViewReady = false override func didMoveToWindow() { super.didMoveToWindow() guard !isViewReady else { return } isViewReady = true selectionStyle = .none constructViewHierarchy() activateConstraints() bindInteraction() contentView.backgroundColor = .clear } private func constructViewHierarchy() { contentView.addSubview(avatarImageView) contentView.addSubview(nameLabel) contentView.addSubview(deleteButton) } private func activateConstraints() { avatarImageView.snp.makeConstraints { make in make.width.height.equalTo(32) make.leading.equalToSuperview().offset(20) make.centerY.equalToSuperview() } nameLabel.snp.makeConstraints { make in make.leading.equalTo(self.avatarImageView.snp.trailing).offset(5) make.centerY.equalToSuperview() } deleteButton.snp.makeConstraints { make in make.trailing.equalToSuperview().offset(-20) make.centerY.equalToSuperview() make.width.height.equalTo(16) } } func updateView(with info: UserInfo) { let placeholder = UIImage(named: "room_default_avatar_rect", in: tuiRoomKitBundle(), compatibleWith: nil) if let url = URL(string: info.avatarUrl) { avatarImageView.sd_setImage(with: url, placeholderImage: placeholder) } else { avatarImageView.image = placeholder } if !info.userName.isEmpty { nameLabel.text = info.userName } else { nameLabel.text = info.userId } } func hideDeleteButton() { self.deleteButton.isHidden = true } func showDeleteButton() { self.deleteButton.isHidden = false } private func bindInteraction() { deleteButton.addTarget(self, action: #selector(deleteButtonTapped(sender:)), for: .touchUpInside) } @objc func deleteButtonTapped(sender: UIButton) { self.delegate?.didDeleteButtonClicked(in: self) } } class SelectedMemberHeaderView: UITableViewHeaderFooterView { override init(reuseIdentifier: String?) { super.init(reuseIdentifier: reuseIdentifier) backgroundView = UIView() backgroundView?.backgroundColor = .white } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private let label: UILabel = { let label = UILabel() label.textColor = UIColor.tui_color(withHex: "4F586B") label.font = UIFont(name: "PingFangSC-Medium", size: 18) label.text = .selectedText label.sizeToFit() return label }() private var isViewReady = false override func didMoveToWindow() { super.didMoveToWindow() guard !isViewReady else { return } isViewReady = true constructViewHierarchy() activateConstraints() } private func constructViewHierarchy() { addSubview(label) } private func activateConstraints() { label.snp.makeConstraints { make in make.leading.equalToSuperview().offset(20) make.top.equalToSuperview() } } func updateLabel(with count: Int) { let text = .selectedText + " (" + "\(count)" + ")" self.label.text = text } } class LargerHitAreaButton: UIButton { var hitAreaEdgeInsets = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10) override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { let largerFrame = bounds.inset(by: hitAreaEdgeInsets) return largerFrame.contains(point) } } private extension String { static var selectedText: String { localized("Selected") } }