// // LNUserSearchUserListView.swift // Lanu // // Created by OpenAI Codex on 2026/3/19. // import Foundation import UIKit import SnapKit import MJRefresh final class LNUserSearchUserListView: UIView { private let emptyView = LNNoMoreDataView() private let tableView = UITableView() private var curKeyword: String? private var nextTag: String? private var curList: [LNGameMateSearchResultVO] = [] override init(frame: CGRect) { super.init(frame: frame) setupViews() } func search(keyword: String) { curKeyword = keyword nextTag = nil curList.removeAll() tableView.reloadData() emptyView.hide() tableView.mj_footer?.resetNoMoreData() tableView.mj_header?.beginRefreshing() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } extension LNUserSearchUserListView { private func searchUser() { guard let curKeyword, !curKeyword.isEmpty else { tableView.mj_header?.endRefreshing() tableView.mj_footer?.endRefreshingWithNoMoreData() return } tableView.isHidden = false LNGameMateManager.shared.searchGameMate(keyword: curKeyword, next: nextTag ?? "") { [weak self] list, next in guard let self else { return } guard let list else { tableView.mj_header?.endRefreshing() tableView.mj_footer?.endRefreshingWithNoMoreData() if curList.isEmpty { emptyView.showNetworkError() } return } if nextTag?.isEmpty != false { curList = list } else { curList.append(contentsOf: list) } nextTag = next tableView.reloadData() if curList.isEmpty { emptyView.showNoData(tips: .init(key: "A00244")) } else { emptyView.hide() } tableView.mj_header?.endRefreshing() if next?.isEmpty != false { tableView.mj_footer?.endRefreshingWithNoMoreData() } else { tableView.mj_footer?.endRefreshing() } } } private func reportExposure() { guard tableView.contentOffset.y >= 0 else { return } guard let indexes = tableView.indexPathsForVisibleRows, !indexes.isEmpty else { return } let items = indexes.map { curList[$0.row] } LNStatisticManager.shared.reportExposure(uids: items.map(\.userNo)) { _ in } } } extension LNUserSearchUserListView: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { curList.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell( withIdentifier: LNUserSearchItemCell.className, for: indexPath ) as! LNUserSearchItemCell cell.update(curList[indexPath.row]) return cell } func scrollViewDidScroll(_ scrollView: UIScrollView) { if scrollView.isDragging { window?.endEditing(true) } } func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { reportExposure() } func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { reportExposure() } func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { if !decelerate { reportExposure() } } } extension LNUserSearchUserListView { private func setupViews() { backgroundColor = .white let header = MJRefreshNormalHeader { [weak self] in self?.nextTag = nil self?.searchUser() } header.lastUpdatedTimeLabel?.isHidden = true header.stateLabel?.isHidden = true header.endRefreshingCompletionBlock = { [weak self] in self?.reportExposure() } tableView.mj_header = header let footer = MJRefreshAutoNormalFooter { [weak self] in self?.searchUser() } footer.setTitle("", for: .noMoreData) footer.setTitle(.init(key: "A00046"), for: .idle) footer.endRefreshingCompletionBlock = { [weak self] in self?.reportExposure() } tableView.mj_footer = footer tableView.isHidden = true tableView.backgroundColor = .clear tableView.separatorStyle = .none tableView.showsVerticalScrollIndicator = false tableView.showsHorizontalScrollIndicator = false tableView.contentInset = .init(top: 6, left: 0, bottom: -commonBottomInset, right: 0) tableView.dataSource = self tableView.delegate = self tableView.allowsSelection = false tableView.register(LNUserSearchItemCell.self, forCellReuseIdentifier: LNUserSearchItemCell.className) addSubview(tableView) tableView.snp.makeConstraints { make in make.edges.equalToSuperview() } tableView.addSubview(emptyView) emptyView.snp.makeConstraints { make in make.centerX.equalToSuperview() make.centerY.equalToSuperview().multipliedBy(0.6) } } } class LNUserSearchItemCell: UITableViewCell { private let cardView = LNUserSearchUserCardView() override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) contentView.addSubview(cardView) cardView.snp.makeConstraints { make in make.edges.equalToSuperview() } LNEventDeliver.addObserver(self) } func update(_ item: LNGameMateSearchResultVO) { cardView.update(item) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } }