// // LNRoomGiftHeaderView.swift // Gami // // Created by OneeChan on 2026/3/23. // import Foundation import UIKit import SnapKit class LNRoomGiftHeaderView: UIView { private let roomSeatsView = UIView() private let stackView = UIStackView() private let specifiedUserView = LNRoomGiftSpecifiedUserView() private weak var roomSession: LNRoomViewModel? private var headers: [LNRoomSeatNum: LNRoomGiftAvatarView] = [:] var selection: [String] { if !roomSeatsView.isHidden { headers.map { $1 }.filter { !$0.isHidden && $0.isSelected }.compactMap { $0.curSeatItem?.uid } } else if let uid = specifiedUserView.curUid { [uid] } else { [] } } override init(frame: CGRect) { super.init(frame: frame) setupViews() LNEventDeliver.addObserver(self) } func update(_ room: LNRoomViewModel?, selectedUid: String?) { roomSession = room if selectedUid == nil || room?.seatsInfo.contains(where: { $0.uid == selectedUid }) == true { onRoomSeatsChanged() roomSeatsView.isHidden = false specifiedUserView.isHidden = true } else { roomSeatsView.isHidden = true specifiedUserView.isHidden = false } if let selectedUid { headers.first { $1.curSeatItem?.uid == selectedUid }?.value.isSelected = true specifiedUserView.update(selectedUid) } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } extension LNRoomGiftHeaderView: LNRoomViewModelNotify { func onRoomSeatsChanged() { guard let seats = roomSession?.seatsInfo.sorted(by: { $0.index.rawValue < $1.index.rawValue }) else { return } if headers.count != seats.count { stackView.arrangedSubviews.forEach { stackView.removeArrangedSubview($0) $0.removeFromSuperview() } for seat in seats { let avatar = LNRoomGiftAvatarView() avatar.isSelected = false avatar.onTap { [weak avatar] in guard let avatar else { return } avatar.isSelected.toggle() } avatar.update(seat) stackView.addArrangedSubview(avatar) headers[seat.index] = avatar } } else { for seat in seats { headers[seat.index]?.update(seat) } } } } private extension LNRoomGiftHeaderView { func setupViews() { let titleLabel = UILabel() titleLabel.font = .body_l titleLabel.textColor = .text_2 titleLabel.text = .init(key: "B00129") addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.leading.equalToSuperview().offset(10) make.centerY.equalToSuperview() } let roomSeatsView = buildRoomSeatsView() addSubview(roomSeatsView) roomSeatsView.snp.makeConstraints { make in make.leading.equalTo(titleLabel.snp.trailing).offset(6) make.trailing.equalToSuperview().offset(-10) make.verticalEdges.equalToSuperview() make.height.equalTo(40) } let userView = buildSpecifiedUserView() addSubview(userView) userView.snp.makeConstraints { make in make.leading.equalTo(titleLabel.snp.trailing).offset(6) make.trailing.equalToSuperview().offset(-10) make.centerY.equalToSuperview() } } private func buildRoomSeatsView() -> UIView { let scrollView = UIScrollView() scrollView.showsHorizontalScrollIndicator = false scrollView.contentInset = .init(top: 0, left: 0, bottom: 0, right: 32) roomSeatsView.addSubview(scrollView) scrollView.snp.makeConstraints { make in make.edges.equalToSuperview() } stackView.spacing = 6 stackView.axis = .horizontal scrollView.addSubview(stackView) stackView.snp.makeConstraints { make in make.edges.equalToSuperview() make.height.equalToSuperview() } let gradientView = UIView.gradientView([ .fill_7.withAlphaComponent(0), .fill_7 ], .horizontalLTR) roomSeatsView.addSubview(gradientView) gradientView.snp.makeConstraints { make in make.verticalEdges.equalToSuperview() make.trailing.equalToSuperview() make.width.equalTo(32) } return roomSeatsView } private func buildSpecifiedUserView() -> UIView { return specifiedUserView } } private class LNRoomGiftAvatarView: UIView { private let avatarBg = UIView() private let avatarView = UIImageView() private let badgeBg = UIView() private let badge = UILabel() private(set) var curSeatItem: LNRoomSeatItem? var isSelected: Bool = false { didSet { if isSelected { avatarBg.layer.borderColor = UIColor.text_6.cgColor badge.textColor = .text_1 badgeBg.backgroundColor = .text_6 } else { avatarBg.layer.borderColor = UIColor.clear.cgColor badge.textColor = .text_4 badgeBg.backgroundColor = .text_1 } } } func update(_ seat: LNRoomSeatItem) { isHidden = seat.uid.isEmpty || seat.uid.isMyUid avatarView.sd_setImage(with: URL(string: seat.avatar)) badge.text = seat.index.giftHeaderTitle curSeatItem = seat } override init(frame: CGRect) { super.init(frame: frame) snp.makeConstraints { make in make.width.height.equalTo(40) } avatarBg.backgroundColor = .clear avatarBg.layer.cornerRadius = 17 avatarBg.layer.borderWidth = 1 avatarBg.layer.borderColor = UIColor.primary_4.cgColor addSubview(avatarBg) avatarBg.snp.makeConstraints { make in make.width.height.equalTo(34) make.centerX.equalToSuperview() make.top.equalToSuperview().offset(1) make.bottom.equalToSuperview().offset(-5) } avatarView.layer.cornerRadius = 15 avatarView.clipsToBounds = true avatarView.contentMode = .scaleAspectFill avatarBg.addSubview(avatarView) avatarView.snp.makeConstraints { make in make.center.equalToSuperview() make.width.height.equalTo(30) } badgeBg.backgroundColor = .fill badgeBg.layer.cornerRadius = 6 addSubview(badgeBg) badgeBg.snp.makeConstraints { make in make.centerX.equalToSuperview() make.bottom.equalToSuperview().offset(-2) make.height.equalTo(12) make.leading.greaterThanOrEqualToSuperview() make.width.greaterThanOrEqualTo(26) } badge.font = .systemFont(ofSize: 10) badge.textColor = .text_4 badge.textAlignment = .center badgeBg.addSubview(badge) badge.snp.makeConstraints { make in make.center.equalToSuperview() make.horizontalEdges.equalToSuperview().inset(4) } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } private class LNRoomGiftSpecifiedUserView: UIView { private let nameLabel = UILabel() private let avatar = UIImageView() private(set) var curUid: String? override init(frame: CGRect) { super.init(frame: frame) let avatarBg = UIView() avatarBg.backgroundColor = .clear avatarBg.layer.cornerRadius = 17 avatarBg.layer.borderWidth = 1 avatarBg.layer.borderColor = UIColor.primary_4.cgColor addSubview(avatarBg) avatarBg.snp.makeConstraints { make in make.width.height.equalTo(34) make.leading.equalToSuperview() make.verticalEdges.equalToSuperview() } avatar.layer.cornerRadius = 15 avatar.clipsToBounds = true avatar.contentMode = .scaleAspectFill avatarBg.addSubview(avatar) avatar.snp.makeConstraints { make in make.center.equalToSuperview() make.width.height.equalTo(30) } nameLabel.font = .body_s nameLabel.textColor = .text_1 addSubview(nameLabel) nameLabel.snp.makeConstraints { make in make.centerY.equalToSuperview() make.leading.equalTo(avatarBg.snp.trailing).offset(5) make.trailing.equalToSuperview() } } func update(_ uid: String?) { curUid = uid guard let uid, !uid.isEmpty else { isHidden = true return } LNProfileManager.shared.getCachedProfileUserInfo(uid: uid, fetchIfNeeded: true) { [weak self] info in guard let self else { return } guard let info, info.uid == curUid else { return } nameLabel.text = info.name avatar.sd_setImage(with: URL(string: info.avatar)) } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } }