// // LNGameMateListCell.swift // Lanu // // Created by OneeChan on 2025/11/16. // import Foundation import UIKit import SnapKit import Combine class LNGameMateListCell: UITableViewCell { private let avatar = UIImageView() private let onlineView = LNOnlineView() private let playButton = UIButton() private let voiceLabel = UILabel() private let voiceWaveView = LNVoiceWaveView() private let priceLabel = UILabel() private let unitLabel = UILabel() private let saleView = UIView() private let discountView = LNNewbieDiscountView() private let nameLabel = UILabel() private let genderView = LNGenderView() private let newbieView = UIView() private let scoreView = UIView() private let scoreLabel = UILabel() private let orderCountLabel = UILabel() private let gameIc = UIImageView() private let gameNameLanel = UILabel() private let bioLabel = UILabel() private let maxShow = 3 private let photoView = UIStackView() private var photos: [UIImageView] = [] private var curItem: LNGameMateListItemVO? override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) setupViews() LNEventDeliver.addObserver(self) } func update(_ item: LNGameMateListItemVO) { avatar.showAvatar(item.avatar) onlineView.isHidden = !item.online priceLabel.text = item.price.toDisplay unitLabel.text = "/\(item.unit)" nameLabel.text = item.nickname genderView.update(item.gender, item.age) scoreLabel.text = "\(item.star)" orderCountLabel.text = .init(key: "A00037", item.orderCount) gameIc.sd_setImage(with: URL(string: item.categoryIcon)) gameNameLanel.text = item.categoryName bioLabel.text = item.summary bioLabel.superview?.isHidden = item.summary.isEmpty newbieView.isHidden = !item.newcomer if item.images.isEmpty { photoView.isHidden = true bioLabel.numberOfLines = 4 } else { for index in 0.. UIView { let container = UIView() avatar.layer.cornerRadius = 38 avatar.clipsToBounds = true avatar.contentMode = .scaleAspectFill container.addSubview(avatar) avatar.snp.makeConstraints { make in make.width.height.equalTo(76) make.top.equalToSuperview() make.horizontalEdges.equalToSuperview() } onlineView.offset = 5 container.addSubview(onlineView) onlineView.snp.makeConstraints { make in make.edges.equalTo(avatar).inset(-2) } playButton.setBackgroundImage(.primary_7, for: .normal) playButton.layer.cornerRadius = 11 playButton.clipsToBounds = true playButton.addAction(UIAction(handler: { [weak self] _ in guard let self else { return } guard let curItem, !curItem.voiceBar.isEmpty else { return } if LNVoicePlayer.shared.playingUrl == curItem.voiceBar { LNVoicePlayer.shared.stop() } else { LNVoicePlayer.shared.play(curItem.voiceBar) } }), for: .touchUpInside) container.addSubview(playButton) playButton.snp.makeConstraints { make in make.centerX.equalToSuperview() make.bottom.equalTo(avatar).offset(10) make.width.equalTo(64) make.height.equalTo(22) } let voice = UIView() voice.isUserInteractionEnabled = false playButton.addSubview(voice) voice.snp.makeConstraints { make in make.center.equalToSuperview() } voiceWaveView.build() voice.addSubview(voiceWaveView) voiceWaveView.snp.makeConstraints { make in make.leading.centerY.equalToSuperview() make.width.equalTo(19) make.height.equalTo(11) } voiceLabel.font = .heading_h5 voiceLabel.textColor = .text_1 voice.addSubview(voiceLabel) voiceLabel.snp.makeConstraints { make in make.verticalEdges.equalToSuperview() make.trailing.equalToSuperview() make.leading.equalTo(voiceWaveView.snp.trailing).offset(4) } let price = UIView() container.addSubview(price) price.snp.makeConstraints { make in make.centerX.equalToSuperview() make.top.equalTo(playButton.snp.bottom).offset(2) } let coin = UIImageView.coinImageView() price.addSubview(coin) coin.snp.makeConstraints { make in make.leading.centerY.equalToSuperview() make.width.height.equalTo(18) } priceLabel.font = .heading_h4 priceLabel.textColor = .text_5 priceLabel.setContentHuggingPriority(.required, for: .vertical) priceLabel.setContentCompressionResistancePriority(.required, for: .vertical) price.addSubview(priceLabel) priceLabel.snp.makeConstraints { make in make.verticalEdges.equalToSuperview() make.leading.equalTo(coin.snp.trailing) } unitLabel.font = .body_s unitLabel.textColor = .text_3 price.addSubview(unitLabel) unitLabel.snp.makeConstraints { make in make.centerY.trailing.equalToSuperview() make.leading.equalTo(priceLabel.snp.trailing).offset(1) } let saleView = buildSaleView() container.addSubview(saleView) saleView.snp.makeConstraints { make in make.centerX.equalToSuperview() make.top.equalTo(price.snp.bottom) make.bottom.equalToSuperview() } return container } private func buildBaseInfo() -> UIView { let container = UIView() container.isUserInteractionEnabled = false nameLabel.font = .heading_h4 nameLabel.textColor = .text_5 nameLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) container.addSubview(nameLabel) nameLabel.snp.makeConstraints { make in make.leading.equalToSuperview() make.top.equalToSuperview() } container.addSubview(genderView) genderView.snp.makeConstraints { make in make.leading.equalTo(nameLabel.snp.trailing).offset(4) make.centerY.equalTo(nameLabel) make.trailing.lessThanOrEqualToSuperview() } container.addSubview(scoreView) scoreView.snp.makeConstraints { make in make.leading.bottom.equalToSuperview() make.top.equalTo(nameLabel.snp.bottom).offset(3) make.height.equalTo(15) } let starIc = UIImageView() starIc.image = .icStarFill scoreView.addSubview(starIc) starIc.snp.makeConstraints { make in make.leading.equalToSuperview() make.width.height.equalTo(14) make.centerY.equalToSuperview() } scoreLabel.font = .heading_h5 scoreLabel.textColor = .text_5 scoreView.addSubview(scoreLabel) scoreLabel.snp.makeConstraints { make in make.leading.equalTo(starIc.snp.trailing).offset(3) make.centerY.equalToSuperview() } orderCountLabel.font = .body_xs orderCountLabel.textColor = .text_3 scoreView.addSubview(orderCountLabel) orderCountLabel.snp.makeConstraints { make in make.leading.equalTo(scoreLabel.snp.trailing).offset(3) make.centerY.equalToSuperview() make.trailing.equalToSuperview() } return container } private func buildNewbieView() -> UIView { newbieView.layer.cornerRadius = 7.5 newbieView.clipsToBounds = true newbieView.snp.makeConstraints { make in make.height.equalTo(15) } let holder = UIView() newbieView.addSubview(holder) holder.snp.makeConstraints { make in make.leading.equalToSuperview() make.verticalEdges.equalToSuperview() } let gradientLayer = CAGradientLayer() gradientLayer.colors = [ UIColor(hex: "#FFD35B").cgColor, UIColor(hex: "#FFFFD35B").cgColor ] gradientLayer.startPoint = .init(x: 0, y: 0) gradientLayer.endPoint = .init(x: 1, y: 0) holder.layer.addSublayer(gradientLayer) holder.publisher(for: \.bounds).removeDuplicates().sink { [weak gradientLayer] newValue in guard let gradientLayer else { return } gradientLayer.frame = newValue }.store(in: &cancellables) let label = UILabel() label.font = .body_xs label.textColor = .init(hex: "#DE9D2E") label.text = .init(key: "A00310") holder.addSubview(label) label.snp.makeConstraints { make in make.leading.equalToSuperview().offset(6) make.trailing.equalToSuperview().offset(-14) make.centerY.equalToSuperview() } return newbieView } private func buildGameView() -> UIView { let container = UIView() container.isUserInteractionEnabled = false let background = UIView() background.backgroundColor = .fill_1 background.layer.cornerRadius = 10 container.addSubview(background) background.snp.makeConstraints { make in make.verticalEdges.equalToSuperview() make.leading.equalToSuperview() } let border = UIImageView() border.image = .primary_7 border.layer.cornerRadius = 8 border.clipsToBounds = true background.addSubview(border) border.snp.makeConstraints { make in make.verticalEdges.equalToSuperview().inset(2) make.leading.equalToSuperview().offset(2) make.width.height.equalTo(16) } gameIc.backgroundColor = .white gameIc.layer.cornerRadius = 7 background.addSubview(gameIc) gameIc.snp.makeConstraints { make in make.center.equalTo(border) make.width.height.equalTo(14) } gameNameLanel.font = .body_xs gameNameLanel.textColor = .text_4 gameNameLanel.setContentHuggingPriority(.required, for: .horizontal) gameNameLanel.setContentCompressionResistancePriority(.required, for: .horizontal) background.addSubview(gameNameLanel) gameNameLanel.snp.makeConstraints { make in make.centerY.equalToSuperview() make.trailing.equalToSuperview().offset(-6) make.leading.equalTo(border.snp.trailing).offset(2) } return container } private func buildBio() -> UIView { let container = UIView() container.isUserInteractionEnabled = false let bg = UIImageView() bg.image = .icGameMateListCellBubble.resizableImage( withCapInsets: .init(top: 16, left: 20, bottom: 12, right: 15), resizingMode: .stretch ) container.addSubview(bg) bg.snp.makeConstraints { make in make.edges.equalToSuperview() make.height.greaterThanOrEqualTo(29) } bioLabel.font = .body_xs bioLabel.textColor = .text_6 container.addSubview(bioLabel) bioLabel.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(8) make.top.equalToSuperview().offset(9) make.bottom.equalToSuperview().offset(-6) } return container } private func buildTextViews() -> UIView { let stackView = UIStackView() stackView.axis = .vertical stackView.spacing = 2 stackView.addArrangedSubview(buildBaseInfo()) stackView.addArrangedSubview(buildNewbieView()) stackView.addArrangedSubview(buildGameView()) stackView.addArrangedSubview(buildBio()) return stackView } private func buildPhotos() -> UIView { photoView.axis = .horizontal photoView.spacing = 6 photoView.distribution = .fillEqually photoView.alignment = .leading for _ in 0.. UIView { let triangle = UIImageView() triangle.image = .icTriangleDown.withRenderingMode(.alwaysTemplate) triangle.tintColor = .init(hex: "#FF6F32") triangle.transform = .init(scaleX: 1, y: -1) saleView.addSubview(triangle) triangle.snp.makeConstraints { make in make.centerX.equalToSuperview() make.top.equalToSuperview() make.width.equalTo(10) make.height.equalTo(4) } discountView.discountOnly = true saleView.addSubview(discountView) discountView.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview() make.bottom.equalToSuperview() make.top.equalTo(triangle.snp.bottom) } return saleView } } #if DEBUG import SwiftUI struct LNGameMateListCellPreview: UIViewRepresentable { func makeUIView(context: Context) -> some UIView { let container = UIView() container.backgroundColor = .lightGray let view = LNGameMateListCell() container.addSubview(view) view.snp.makeConstraints { make in make.leading.trailing.centerY.equalToSuperview() make.height.equalTo(187) } return container } func updateUIView(_ uiView: UIViewType, context: Context) { } } #Preview(body: { LNGameMateListCellPreview() }) #endif