LNUserSearchUserListView.swift 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. //
  2. // LNUserSearchUserListView.swift
  3. // Lanu
  4. //
  5. // Created by OpenAI Codex on 2026/3/19.
  6. //
  7. import Foundation
  8. import UIKit
  9. import SnapKit
  10. import MJRefresh
  11. final class LNUserSearchUserListView: UIView {
  12. private let emptyView = LNNoMoreDataView()
  13. private let tableView = UITableView()
  14. private var curKeyword: String?
  15. private var nextTag: String?
  16. private var curList: [LNGameMateSearchResultVO] = []
  17. override init(frame: CGRect) {
  18. super.init(frame: frame)
  19. setupViews()
  20. }
  21. func search(keyword: String) {
  22. curKeyword = keyword
  23. nextTag = nil
  24. curList.removeAll()
  25. tableView.reloadData()
  26. emptyView.hide()
  27. tableView.mj_footer?.resetNoMoreData()
  28. tableView.mj_header?.beginRefreshing()
  29. }
  30. required init?(coder: NSCoder) {
  31. fatalError("init(coder:) has not been implemented")
  32. }
  33. }
  34. extension LNUserSearchUserListView {
  35. private func searchUser() {
  36. guard let curKeyword, !curKeyword.isEmpty else {
  37. tableView.mj_header?.endRefreshing()
  38. tableView.mj_footer?.endRefreshingWithNoMoreData()
  39. return
  40. }
  41. tableView.isHidden = false
  42. LNGameMateManager.shared.searchGameMate(keyword: curKeyword, next: nextTag ?? "") { [weak self] list, next in
  43. guard let self else { return }
  44. guard let list else {
  45. tableView.mj_header?.endRefreshing()
  46. tableView.mj_footer?.endRefreshingWithNoMoreData()
  47. if curList.isEmpty {
  48. emptyView.showNetworkError()
  49. }
  50. return
  51. }
  52. if nextTag?.isEmpty != false {
  53. curList = list
  54. } else {
  55. curList.append(contentsOf: list)
  56. }
  57. nextTag = next
  58. tableView.reloadData()
  59. if curList.isEmpty {
  60. emptyView.showNoData(tips: .init(key: "A00244"))
  61. } else {
  62. emptyView.hide()
  63. }
  64. tableView.mj_header?.endRefreshing()
  65. if next?.isEmpty != false {
  66. tableView.mj_footer?.endRefreshingWithNoMoreData()
  67. } else {
  68. tableView.mj_footer?.endRefreshing()
  69. }
  70. }
  71. }
  72. private func reportExposure() {
  73. guard tableView.contentOffset.y >= 0 else { return }
  74. guard let indexes = tableView.indexPathsForVisibleRows, !indexes.isEmpty else { return }
  75. let items = indexes.map { curList[$0.row] }
  76. LNStatisticManager.shared.reportExposure(uids: items.map(\.userNo)) { _ in }
  77. }
  78. }
  79. extension LNUserSearchUserListView: UITableViewDataSource, UITableViewDelegate {
  80. func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  81. curList.count
  82. }
  83. func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  84. let cell = tableView.dequeueReusableCell(
  85. withIdentifier: LNUserSearchItemCell.className,
  86. for: indexPath
  87. ) as! LNUserSearchItemCell
  88. cell.update(curList[indexPath.row])
  89. return cell
  90. }
  91. func scrollViewDidScroll(_ scrollView: UIScrollView) {
  92. if scrollView.isDragging {
  93. window?.endEditing(true)
  94. }
  95. }
  96. func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
  97. reportExposure()
  98. }
  99. func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
  100. reportExposure()
  101. }
  102. func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
  103. if !decelerate {
  104. reportExposure()
  105. }
  106. }
  107. }
  108. extension LNUserSearchUserListView {
  109. private func setupViews() {
  110. backgroundColor = .white
  111. let header = MJRefreshNormalHeader { [weak self] in
  112. self?.nextTag = nil
  113. self?.searchUser()
  114. }
  115. header.lastUpdatedTimeLabel?.isHidden = true
  116. header.stateLabel?.isHidden = true
  117. header.endRefreshingCompletionBlock = { [weak self] in
  118. self?.reportExposure()
  119. }
  120. tableView.mj_header = header
  121. let footer = MJRefreshAutoNormalFooter { [weak self] in
  122. self?.searchUser()
  123. }
  124. footer.setTitle("", for: .noMoreData)
  125. footer.setTitle(.init(key: "A00046"), for: .idle)
  126. footer.endRefreshingCompletionBlock = { [weak self] in
  127. self?.reportExposure()
  128. }
  129. tableView.mj_footer = footer
  130. tableView.isHidden = true
  131. tableView.backgroundColor = .clear
  132. tableView.separatorStyle = .none
  133. tableView.showsVerticalScrollIndicator = false
  134. tableView.showsHorizontalScrollIndicator = false
  135. tableView.contentInset = .init(top: 6, left: 0, bottom: -commonBottomInset, right: 0)
  136. tableView.dataSource = self
  137. tableView.delegate = self
  138. tableView.allowsSelection = false
  139. tableView.register(LNUserSearchItemCell.self, forCellReuseIdentifier: LNUserSearchItemCell.className)
  140. addSubview(tableView)
  141. tableView.snp.makeConstraints { make in
  142. make.edges.equalToSuperview()
  143. }
  144. tableView.addSubview(emptyView)
  145. emptyView.snp.makeConstraints { make in
  146. make.centerX.equalToSuperview()
  147. make.centerY.equalToSuperview().multipliedBy(0.6)
  148. }
  149. }
  150. }
  151. class LNUserSearchItemCell: UITableViewCell {
  152. private let cardView = LNUserSearchUserCardView()
  153. override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
  154. super.init(style: style, reuseIdentifier: reuseIdentifier)
  155. contentView.addSubview(cardView)
  156. cardView.snp.makeConstraints { make in
  157. make.edges.equalToSuperview()
  158. }
  159. LNEventDeliver.addObserver(self)
  160. }
  161. func update(_ item: LNGameMateSearchResultVO) {
  162. cardView.update(item)
  163. }
  164. required init?(coder: NSCoder) {
  165. fatalError("init(coder:) has not been implemented")
  166. }
  167. }