// // LNInterestSetupViewController.swift // Lanu // // Created by OneeChan on 2025/12/3. // import Foundation import UIKit import SnapKit extension UIView { func pushToInterestSetup(_ config: LNProfileUpdateConfig) { let vc = LNInterestSetupViewController(config: config) navigationController?.pushViewController(vc, animated: true) } } class LNInterestSetupViewController: LNViewController { private let updateConfig: LNProfileUpdateConfig private let horizentalSpace = 22.0 private let lineSpace = 8.0 private let itemSpacing = 13.0 private let minWidth = 115.0 private let itemHeight = 85.0 static let headerHeight = 44.0 private let collectionViewLayout = UICollectionViewFlowLayout() private let collectionView: UICollectionView private let nextButton = UIButton() private let bottomGradient = CAGradientLayer() private let maxCount = LNGameMateManager.gameSelectionMaxCount private var selectedIndexs: [IndexPath] = [] private var games: [LNGameTypeItemVO] = LNGameMateManager.shared.curGameTypes init(config: LNProfileUpdateConfig) { updateConfig = config collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() setupViews() } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() if let superlayer = bottomGradient.superlayer { bottomGradient.frame = superlayer.bounds } } } extension LNInterestSetupViewController: UICollectionViewDataSource, UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { games[section].children.count } func numberOfSections(in collectionView: UICollectionView) -> Int { games.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: LNInterestItemCell.className, for: indexPath) as! LNInterestItemCell let item = games[indexPath.section].children[indexPath.row] cell.update(item) cell.inSelected = selectedIndexs.contains(indexPath) cell.isEnable = selectedIndexs.count < maxCount || selectedIndexs.contains(indexPath) return cell } func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { guard kind == UICollectionView.elementKindSectionHeader else { return UICollectionReusableView() } let view = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: LNInterestGroupHeader.className, for: indexPath) as! LNInterestGroupHeader let section = games[indexPath.section] view.update(section.name) return view } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { let needReload: Bool if selectedIndexs.contains(indexPath) { needReload = selectedIndexs.count == maxCount selectedIndexs.removeAll { $0 == indexPath } } else if selectedIndexs.count < maxCount { selectedIndexs.append(indexPath) needReload = selectedIndexs.count == maxCount } else { needReload = false } if needReload { collectionView.reloadData() } else { collectionView.reloadItems(at: [indexPath]) } checkNextButtonEnable() } } extension LNInterestSetupViewController { private func checkNextButtonEnable() { nextButton.isEnabled = selectedIndexs.count > 0 } private func setupViews() { view.backgroundColor = .primary_1 navigationBarColor = .clear let bg = UIImageView() bg.image = .icLoginInterestBg view.addSubview(bg) bg.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview() make.top.equalTo(fakeNaviBgView) } let titleView = buildTitles() view.addSubview(titleView) titleView.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(22) make.top.equalToSuperview().offset(22) } let listView = buildListView() view.addSubview(listView) listView.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(horizentalSpace) make.top.equalTo(titleView.snp.bottom).offset(6) } let bottom = buildBottomView() view.addSubview(bottom) bottom.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview() make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom) make.top.equalTo(listView.snp.bottom).offset(-16) } } private func buildTitles() -> UIView { let stackView = UIStackView() stackView.axis = .vertical stackView.spacing = 6 let mainLabel = UILabel() mainLabel.text = .init(key: "A00111") mainLabel.font = .heading_h1 mainLabel.textColor = .text_5 mainLabel.numberOfLines = 0 stackView.addArrangedSubview(mainLabel) let descLabel = UILabel() descLabel.font = .body_m descLabel.textColor = .text_3 descLabel.text = .init(key: "A00112") descLabel.numberOfLines = 0 stackView.addArrangedSubview(descLabel) return stackView } private func buildListView() -> UIView { let count = floor(CGFloat((view.bounds.width - horizentalSpace * 2) + itemSpacing) / minWidth) let itemWidth = (view.bounds.width - horizentalSpace * 2 + itemSpacing) / count - itemSpacing collectionViewLayout.itemSize = .init(width: itemWidth, height: itemHeight) collectionViewLayout.minimumLineSpacing = lineSpace collectionViewLayout.minimumInteritemSpacing = itemSpacing collectionViewLayout.headerReferenceSize = .init(width: view.bounds.width - horizentalSpace * 2, height: Self.headerHeight) // collectionViewLayout.sectionHeadersPinToVisibleBounds = true collectionView.backgroundColor = .clear collectionView.dataSource = self collectionView.delegate = self collectionView.showsHorizontalScrollIndicator = false collectionView.showsVerticalScrollIndicator = false collectionView.register(LNInterestItemCell.self, forCellWithReuseIdentifier: LNInterestItemCell.className) collectionView.register(LNInterestGroupHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: LNInterestGroupHeader.className) collectionView.contentInset = .init(top: 0, left: 0, bottom: 20, right: 0) return collectionView } private func buildBottomView() -> UIView { let container = UIView() bottomGradient.colors = [ UIColor.white.withAlphaComponent(0).cgColor, UIColor.primary_1.cgColor, UIColor.primary_1.cgColor ] bottomGradient.locations = [0, 0.3, 1] bottomGradient.startPoint = .init(x: 0, y: 0) bottomGradient.endPoint = .init(x: 0, y: 1) container.layer.addSublayer(bottomGradient) nextButton.setBackgroundImage(.primary_8, for: .normal) nextButton.setTitle(.init(key: "A00113"), for: .normal) nextButton.setTitleColor(.text_1, for: .normal) nextButton.titleLabel?.font = .heading_h3 nextButton.layer.cornerRadius = 23.5 nextButton.clipsToBounds = true nextButton.isEnabled = false nextButton.addAction(UIAction(handler: { [weak self] _ in guard let self else { return } let interests = selectedIndexs.compactMap { self.games[$0.section].children[$0.row].code } updateConfig.interest = interests LNProfileManager.shared.modifyMyProfile(config: updateConfig) { [weak self] success in guard let self else { return } guard success else { return } navigationController?.popToRootViewController(animated: true) LNGameMateManager.shared.getGameTypeList { _ in } } }), for: .touchUpInside) container.addSubview(nextButton) nextButton.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(16) make.top.equalToSuperview().offset(16) make.bottom.equalToSuperview().offset(-4) make.height.equalTo(47) } return container } } private class LNInterestItemCell: UICollectionViewCell { private let ic = UIImageView() private let nameLabel = UILabel() private let bg = UIImageView() var inSelected: Bool = false { didSet { bg.image = inSelected ? .icLoginInterestItemSelected : .icLoginInterestItem } } var isEnable: Bool = true { didSet { contentView.alpha = isEnable ? 1.0 : 0.5 isUserInteractionEnabled = isEnable } } override init(frame: CGRect) { super.init(frame: frame) contentView.addSubview(bg) bg.snp.makeConstraints { make in make.center.equalToSuperview() make.width.equalTo(102) make.height.equalTo(85) } ic.layer.cornerRadius = 25 ic.clipsToBounds = true ic.backgroundColor = .white bg.addSubview(ic) ic.snp.makeConstraints { make in make.centerX.equalToSuperview() make.top.equalToSuperview().offset(7) make.width.height.equalTo(50) } nameLabel.font = .body_s nameLabel.textColor = .text_5 nameLabel.textAlignment = .center bg.addSubview(nameLabel) nameLabel.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(6) make.bottom.equalToSuperview().offset(-6) } } func update(_ item: LNGameCategoryItemVO) { ic.sd_setImage(with: URL(string: item.icon)) nameLabel.text = item.name let curState = isSelected isSelected = curState } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } private class LNInterestGroupHeader: UICollectionReusableView { private let titleLabel = UILabel() override init(frame: CGRect) { super.init(frame: frame) snp.makeConstraints { make in make.height.equalTo(LNInterestSetupViewController.headerHeight) } titleLabel.font = .heading_h2 titleLabel.textColor = .text_5 addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.bottom.equalToSuperview().offset(-8) make.leading.equalToSuperview() } } func update(_ title: String) { titleLabel.text = title } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } }