LNGameMateListView.swift 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. //
  2. // LNGameMateListView.swift
  3. // Lanu
  4. //
  5. // Created by OneeChan on 2025/11/14.
  6. //
  7. import Foundation
  8. import UIKit
  9. import SnapKit
  10. import Combine
  11. import MJRefresh
  12. class LNGameMateListView: UIView {
  13. private(set) var curMateList: [LNGameMateListItemVO] = []
  14. private var topCategory: String = ""
  15. private var category: String?
  16. private var filter = LNGameMateFilter()
  17. private var nextTag: String?
  18. private let pageSize = 30
  19. private let emptyView = LNNoMoreDataView()
  20. private let tableView = UITableView()
  21. private var loading = false
  22. override init(frame: CGRect) {
  23. super.init(frame: frame)
  24. setupViews()
  25. }
  26. func loadListIfNeed() {
  27. if !curMateList.isEmpty { return }
  28. tableView.mj_header?.beginRefreshing()
  29. }
  30. func reloadList(newTopCategory: String, newCategory: String?, filter: LNGameMateFilter) {
  31. topCategory = newTopCategory
  32. category = newCategory
  33. self.filter = filter
  34. tableView.mj_header?.beginRefreshing()
  35. }
  36. required init?(coder: NSCoder) {
  37. fatalError("init(coder:) has not been implemented")
  38. }
  39. }
  40. extension LNGameMateListView {
  41. private func loadList() {
  42. guard !loading else { return }
  43. loading = true
  44. let curNext = nextTag ?? ""
  45. LNGameMateManager.shared.getGameMateList(
  46. topCategory: topCategory, category: category,
  47. filter: filter, size: pageSize, next: curNext
  48. ) { [weak self] list, nextTag in
  49. guard let self else { return }
  50. loading = false
  51. if let list {
  52. if curNext.isEmpty {
  53. curMateList.removeAll()
  54. }
  55. curMateList.append(contentsOf: list)
  56. self.nextTag = nextTag
  57. tableView.reloadData()
  58. if curMateList.isEmpty {
  59. emptyView.showNoData(tips: .init(key: "A00039"))
  60. } else {
  61. emptyView.hide()
  62. }
  63. } else {
  64. if curMateList.isEmpty {
  65. emptyView.showNetworkError()
  66. }
  67. }
  68. self.tableView.mj_header?.endRefreshing()
  69. if nextTag?.isEmpty != false {
  70. tableView.mj_footer?.endRefreshingWithNoMoreData()
  71. } else {
  72. tableView.mj_footer?.endRefreshing()
  73. }
  74. }
  75. }
  76. func reportExposure() {
  77. let frame = convert(bounds, to: nil)
  78. guard frame.origin.x == 0,
  79. tableView.contentOffset.y >= 0 else {
  80. return
  81. }
  82. guard let indexs = tableView.indexPathsForVisibleRows,
  83. !indexs.isEmpty else {
  84. return
  85. }
  86. var items = Set<String>()
  87. for index in indexs {
  88. items.insert(curMateList[index.row].userNo)
  89. }
  90. LNStatisticManager.shared.reportExposure(uids: Array(items)) { _ in }
  91. }
  92. }
  93. extension LNGameMateListView: LNGameFilterPanelDelegate {
  94. func onGameFilterPanel(panel: LNGameFilterPanel, didSelectFilter filter: LNGameMateFilter,
  95. topType: LNGameTypeItemVO, category: LNGameCategoryItemVO) {
  96. if viewController is LNHomeViewController {
  97. pushToGameMateList(topCategory: topType, category: category, filter: filter)
  98. } else {
  99. reloadList(newTopCategory: topType.code, newCategory: category.code, filter: filter)
  100. viewController?.title = category.name
  101. }
  102. }
  103. }
  104. extension LNGameMateListView: LNGameMateListMenuViewDelegate {
  105. func menuView(view: LNGameMateListMenuView, scoreTypeChanged newType: LNSortedType) {
  106. filter.sortByStar = newType
  107. tableView.mj_header?.beginRefreshing()
  108. }
  109. func menuView(view: LNGameMateListMenuView, priceTypeChanged newType: LNSortedType) {
  110. filter.sortByPrice = newType
  111. tableView.mj_header?.beginRefreshing()
  112. }
  113. func menuViewDidClickFind(view: LNGameMateListMenuView) {
  114. let filterPanel = LNGameFilterPanel()
  115. filterPanel.delegate = self
  116. filterPanel.popup()
  117. }
  118. }
  119. extension LNGameMateListView: UITableViewDataSource, UITableViewDelegate {
  120. func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  121. curMateList.count
  122. }
  123. func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  124. let cell = tableView.dequeueReusableCell(withIdentifier: LNGameMateListCell.className, for: indexPath) as! LNGameMateListCell
  125. let data = curMateList[indexPath.row]
  126. cell.update(data)
  127. return cell
  128. }
  129. func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
  130. reportExposure()
  131. }
  132. func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
  133. reportExposure()
  134. }
  135. func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
  136. if !decelerate {
  137. reportExposure()
  138. }
  139. }
  140. }
  141. extension LNGameMateListView {
  142. private func setupViews() {
  143. let menu = buildMenu()
  144. addSubview(menu)
  145. menu.snp.makeConstraints { make in
  146. make.leading.equalToSuperview().offset(16)
  147. make.trailing.equalToSuperview().offset(-16)
  148. make.top.equalToSuperview()
  149. }
  150. let listView = buildListView()
  151. addSubview(listView)
  152. listView.snp.makeConstraints { make in
  153. make.leading.trailing.bottom.equalToSuperview()
  154. make.top.equalTo(menu.snp.bottom).offset(10)
  155. }
  156. }
  157. private func buildMenu() -> UIView {
  158. let menu = LNGameMateListMenuView()
  159. menu.delegate = self
  160. return menu
  161. }
  162. private func buildListView() -> UIView {
  163. let header = MJRefreshNormalHeader { [weak self] in
  164. guard let self else { return }
  165. self.nextTag = nil
  166. self.loadList()
  167. }
  168. header.lastUpdatedTimeLabel?.isHidden = true
  169. header.stateLabel?.isHidden = true
  170. header.endRefreshingCompletionBlock = { [weak self] in
  171. guard let self else { return }
  172. reportExposure()
  173. }
  174. tableView.mj_header = header
  175. let footer = MJRefreshAutoNormalFooter { [weak self] in
  176. guard let self else { return }
  177. self.loadList()
  178. }
  179. footer.setTitle("", for: .noMoreData)
  180. footer.setTitle(.init(key: "A00046"), for: .idle)
  181. footer.endRefreshingCompletionBlock = { [weak self] in
  182. guard let self else { return }
  183. reportExposure()
  184. }
  185. tableView.mj_footer = footer
  186. tableView.register(LNGameMateListCell.self, forCellReuseIdentifier: LNGameMateListCell.className)
  187. tableView.dataSource = self
  188. tableView.delegate = self
  189. tableView.allowsSelection = false
  190. tableView.separatorStyle = .none
  191. tableView.backgroundColor = .clear
  192. tableView.addSubview(emptyView)
  193. emptyView.snp.makeConstraints { make in
  194. make.centerX.equalToSuperview()
  195. make.centerY.equalToSuperview().multipliedBy(0.6)
  196. }
  197. return tableView
  198. }
  199. }
  200. #if DEBUG
  201. import SwiftUI
  202. struct LNGameMateListViewPreview: UIViewRepresentable {
  203. func makeUIView(context: Context) -> some UIView {
  204. let container = UIView()
  205. container.backgroundColor = .lightGray
  206. let view = LNGameMateListView()
  207. container.addSubview(view)
  208. view.snp.makeConstraints { make in
  209. make.leading.trailing.equalToSuperview()
  210. }
  211. return container
  212. }
  213. func updateUIView(_ uiView: UIViewType, context: Context) { }
  214. }
  215. #Preview(body: {
  216. LNGameMateListViewPreview()
  217. })
  218. #endif