|
|
@@ -8,7 +8,6 @@
|
|
|
import Foundation
|
|
|
import UIKit
|
|
|
import SnapKit
|
|
|
-import MJRefresh
|
|
|
|
|
|
|
|
|
extension UIView {
|
|
|
@@ -18,157 +17,123 @@ extension UIView {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+enum LNUserSearchTab: Int, CaseIterable {
|
|
|
+ case overview
|
|
|
+ case user
|
|
|
+ case rooms
|
|
|
+
|
|
|
+ var title: String {
|
|
|
+ switch self {
|
|
|
+ case .overview:
|
|
|
+ .init(key: "A00363")
|
|
|
+ case .user:
|
|
|
+ .init(key: "A00364")
|
|
|
+ case .rooms:
|
|
|
+ .init(key: "A00365")
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
class LNUserSearchViewController: LNViewController {
|
|
|
private let searchInput = UITextField()
|
|
|
|
|
|
private let historyView = LNUserSearchHistoryView()
|
|
|
|
|
|
- private let emptyView = LNNoMoreDataView()
|
|
|
- private let tableView = UITableView()
|
|
|
-
|
|
|
- private var curKeyword: String?
|
|
|
- private var nextTag: String?
|
|
|
- private var curList: [LNGameMateSearchResultVO] = []
|
|
|
+ private let searchView = UIView()
|
|
|
+ private let scrollView = UIScrollView()
|
|
|
+ private let tabsView = LNUserSearchTabsView()
|
|
|
+ private let overviewListView = LNUserSearchOverviewListView()
|
|
|
+ private let userListView = LNUserSearchUserListView()
|
|
|
+ private let roomListView = LNUserSearchRoomListView()
|
|
|
+ private lazy var resultViews = [overviewListView, userListView, roomListView]
|
|
|
|
|
|
override func viewDidLoad() {
|
|
|
super.viewDidLoad()
|
|
|
|
|
|
setupViews()
|
|
|
+ showLandingState()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-extension LNUserSearchViewController {
|
|
|
- private func searchUser() {
|
|
|
- guard let curKeyword else { return }
|
|
|
- view.pushToRoom(curKeyword)
|
|
|
-
|
|
|
-// guard let curKeyword, !curKeyword.isEmpty else {
|
|
|
-// tableView.mj_header?.endRefreshing()
|
|
|
-// tableView.mj_footer?.endRefreshingWithNoMoreData()
|
|
|
-// return
|
|
|
-// }
|
|
|
-// view.endEditing(true)
|
|
|
-//
|
|
|
- historyView.isHidden = true
|
|
|
- historyView.addRecord(curKeyword)
|
|
|
-//
|
|
|
-// 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()
|
|
|
-// }
|
|
|
-//
|
|
|
-// self.tableView.mj_header?.endRefreshing()
|
|
|
-// if next?.isEmpty != false {
|
|
|
-// tableView.mj_footer?.endRefreshingWithNoMoreData()
|
|
|
-// } else {
|
|
|
-// tableView.mj_footer?.endRefreshing()
|
|
|
-// }
|
|
|
-// }
|
|
|
- }
|
|
|
-
|
|
|
- func reportExposure() {
|
|
|
- guard tableView.contentOffset.y >= 0 else {
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- guard let indexs = tableView.indexPathsForVisibleRows,
|
|
|
- !indexs.isEmpty else {
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- var items: [LNGameMateSearchResultVO] = []
|
|
|
- for index in indexs {
|
|
|
- items.append(curList[index.row])
|
|
|
- }
|
|
|
-
|
|
|
- LNStatisticManager.shared.reportExposure(uids: items.map({ $0.userNo })) { _ in }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-extension LNUserSearchViewController: 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
|
|
|
-
|
|
|
- let item = curList[indexPath.row]
|
|
|
- cell.update(item)
|
|
|
-
|
|
|
- return cell
|
|
|
- }
|
|
|
-
|
|
|
- func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
|
|
- if searchInput.isFirstResponder {
|
|
|
- view.endEditing(true)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
+extension LNUserSearchViewController: UIScrollViewDelegate {
|
|
|
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
|
|
- reportExposure()
|
|
|
+ syncTabFromPageScroll()
|
|
|
}
|
|
|
|
|
|
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
|
|
|
- reportExposure()
|
|
|
- }
|
|
|
-
|
|
|
- func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
|
|
- if !decelerate {
|
|
|
- reportExposure()
|
|
|
- }
|
|
|
+ syncTabFromPageScroll()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
extension LNUserSearchViewController: LNUserSearchHistoryViewDelegate {
|
|
|
func onUserSearchHistoryView(view: LNUserSearchHistoryView, didClick history: String) {
|
|
|
searchInput.text = history
|
|
|
- curKeyword = history
|
|
|
- nextTag = nil
|
|
|
- curList.removeAll()
|
|
|
- tableView.reloadData()
|
|
|
- tableView.mj_header?.beginRefreshing()
|
|
|
+ triggerSearchIfNeeded()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
extension LNUserSearchViewController: UITextFieldDelegate {
|
|
|
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
|
|
- textField.resignFirstResponder()
|
|
|
- guard let text = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
|
- !text.isEmpty else { return true }
|
|
|
+ triggerSearchIfNeeded()
|
|
|
+ return true
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+extension LNUserSearchViewController: LNUserSearchOverviewListViewDelegate {
|
|
|
+ func onUserSearchOverviewListView(view: LNUserSearchOverviewListView, didClickMore tab: LNUserSearchTab) {
|
|
|
+ selectTab(tab, animated: true)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+extension LNUserSearchViewController {
|
|
|
+ private func triggerSearchIfNeeded() {
|
|
|
+ searchInput.resignFirstResponder()
|
|
|
|
|
|
- curKeyword = text
|
|
|
- nextTag = nil
|
|
|
- curList.removeAll()
|
|
|
- tableView.reloadData()
|
|
|
- tableView.mj_header?.beginRefreshing()
|
|
|
+ let keyword = searchInput.text?
|
|
|
+ .trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
|
|
+ if keyword.isEmpty {
|
|
|
+ showLandingState()
|
|
|
+ return
|
|
|
+ }
|
|
|
|
|
|
- return true
|
|
|
+ searchInput.text = keyword
|
|
|
+ historyView.addRecord(keyword)
|
|
|
+
|
|
|
+ overviewListView.search(keyword)
|
|
|
+ userListView.search(keyword: keyword)
|
|
|
+ roomListView.search(keyword: keyword)
|
|
|
+
|
|
|
+ searchView.isHidden = false
|
|
|
+ historyView.isHidden = true
|
|
|
+
|
|
|
+ userListView.search(keyword: keyword)
|
|
|
+ selectTab(.overview, animated: false)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func showLandingState() {
|
|
|
+ searchView.isHidden = true
|
|
|
+ historyView.isHidden = false
|
|
|
+ }
|
|
|
+
|
|
|
+ private func selectTab(_ tab: LNUserSearchTab, animated: Bool) {
|
|
|
+ tabsView.update(selected: tab, animated: animated)
|
|
|
+
|
|
|
+ guard scrollView.bounds.width > 0 else {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ let offsetX: CGFloat = scrollView.bounds.width * CGFloat(tab.rawValue)
|
|
|
+ scrollView.setContentOffset(.init(x: offsetX, y: 0), animated: animated)
|
|
|
+ if !animated {
|
|
|
+ syncTabFromPageScroll()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private func syncTabFromPageScroll() {
|
|
|
+ guard scrollView.bounds.width > 0 else { return }
|
|
|
+ let page = Int(round(scrollView.contentOffset.x / scrollView.bounds.width))
|
|
|
+ guard let tab = LNUserSearchTab(rawValue: max(0, min(page, LNUserSearchTab.allCases.count - 1))) else { return }
|
|
|
+ tabsView.update(selected: tab, animated: true)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -176,24 +141,21 @@ extension LNUserSearchViewController {
|
|
|
private func setupViews() {
|
|
|
setupNavBar()
|
|
|
|
|
|
- let history = buildHistory()
|
|
|
- view.addSubview(history)
|
|
|
- history.snp.makeConstraints { make in
|
|
|
- make.horizontalEdges.equalToSuperview().inset(16)
|
|
|
- make.top.equalToSuperview().offset(12)
|
|
|
+ historyView.delegate = self
|
|
|
+ view.addSubview(historyView)
|
|
|
+ historyView.snp.makeConstraints { make in
|
|
|
+ make.horizontalEdges.equalToSuperview()
|
|
|
+ make.top.equalToSuperview()
|
|
|
}
|
|
|
|
|
|
- let list = buildList()
|
|
|
- view.addSubview(list)
|
|
|
- list.snp.makeConstraints { make in
|
|
|
- make.horizontalEdges.equalToSuperview().inset(16)
|
|
|
- make.top.equalToSuperview().offset(12)
|
|
|
- make.bottom.equalToSuperview()
|
|
|
+ let resultView = buildResultView()
|
|
|
+ view.addSubview(resultView)
|
|
|
+ resultView.snp.makeConstraints { make in
|
|
|
+ make.edges.equalToSuperview()
|
|
|
}
|
|
|
|
|
|
view.onTap { [weak self] in
|
|
|
- guard let self else { return }
|
|
|
- view.endEditing(true)
|
|
|
+ self?.view.endEditing(true)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -205,8 +167,7 @@ extension LNUserSearchViewController {
|
|
|
search.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
|
|
search.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
|
|
|
search.addAction(UIAction(handler: { [weak self] _ in
|
|
|
- guard let self else { return }
|
|
|
- _ = textFieldShouldReturn(searchInput)
|
|
|
+ self?.triggerSearchIfNeeded()
|
|
|
}), for: .touchUpInside)
|
|
|
setRightButton(search)
|
|
|
|
|
|
@@ -223,16 +184,17 @@ extension LNUserSearchViewController {
|
|
|
make.width.equalTo(view.bounds.width).priority(.medium)
|
|
|
}
|
|
|
|
|
|
- let ic = UIImageView()
|
|
|
- ic.image = .icMagnifyingglass
|
|
|
- container.addSubview(ic)
|
|
|
- ic.snp.makeConstraints { make in
|
|
|
+ let icon = UIImageView()
|
|
|
+ icon.image = .icMagnifyingglass
|
|
|
+ container.addSubview(icon)
|
|
|
+ icon.snp.makeConstraints { make in
|
|
|
make.leading.equalToSuperview().offset(10)
|
|
|
make.centerY.equalToSuperview()
|
|
|
make.width.height.equalTo(18)
|
|
|
}
|
|
|
|
|
|
searchInput.font = .body_s
|
|
|
+ searchInput.textColor = .text_5
|
|
|
searchInput.placeholder = .init(key: "A00246")
|
|
|
searchInput.clearButtonMode = .always
|
|
|
searchInput.returnKeyType = .search
|
|
|
@@ -242,13 +204,12 @@ extension LNUserSearchViewController {
|
|
|
searchInput.addAction(UIAction(handler: { [weak self] _ in
|
|
|
guard let self else { return }
|
|
|
if searchInput.text?.isEmpty != false {
|
|
|
- historyView.isHidden = false
|
|
|
- tableView.isHidden = true
|
|
|
+ showLandingState()
|
|
|
}
|
|
|
}), for: .editingChanged)
|
|
|
container.addSubview(searchInput)
|
|
|
searchInput.snp.makeConstraints { make in
|
|
|
- make.leading.equalTo(ic.snp.trailing).offset(8)
|
|
|
+ make.leading.equalTo(icon.snp.trailing).offset(8)
|
|
|
make.centerY.equalToSuperview()
|
|
|
make.trailing.equalToSuperview().offset(-10)
|
|
|
}
|
|
|
@@ -256,57 +217,41 @@ extension LNUserSearchViewController {
|
|
|
return container
|
|
|
}
|
|
|
|
|
|
- private func buildList() -> UIView {
|
|
|
- let header = MJRefreshNormalHeader { [weak self] in
|
|
|
- guard let self else { return }
|
|
|
- nextTag = nil
|
|
|
- searchUser()
|
|
|
+ private func buildResultView() -> UIView {
|
|
|
+ tabsView.onSelect = { [weak self] tab in
|
|
|
+ self?.selectTab(tab, animated: true)
|
|
|
}
|
|
|
- header.lastUpdatedTimeLabel?.isHidden = true
|
|
|
- header.stateLabel?.isHidden = true
|
|
|
- header.endRefreshingCompletionBlock = { [weak self] in
|
|
|
- guard let self else { return }
|
|
|
- reportExposure()
|
|
|
+ searchView.addSubview(tabsView)
|
|
|
+ tabsView.snp.makeConstraints { make in
|
|
|
+ make.horizontalEdges.equalToSuperview()
|
|
|
+ make.top.equalToSuperview()
|
|
|
}
|
|
|
- tableView.mj_header = header
|
|
|
|
|
|
- let footer = MJRefreshAutoNormalFooter { [weak self] in
|
|
|
- guard let self else { return }
|
|
|
- searchUser()
|
|
|
- }
|
|
|
- footer.setTitle("", for: .noMoreData)
|
|
|
- footer.setTitle(.init(key: "A00046"), for: .idle)
|
|
|
- footer.endRefreshingCompletionBlock = { [weak self] in
|
|
|
- guard let self else { return }
|
|
|
- reportExposure()
|
|
|
+ scrollView.isPagingEnabled = true
|
|
|
+ scrollView.delegate = self
|
|
|
+ searchView.addSubview(scrollView)
|
|
|
+ scrollView.snp.makeConstraints { make in
|
|
|
+ make.horizontalEdges.equalToSuperview()
|
|
|
+ make.bottom.equalToSuperview()
|
|
|
+ make.top.equalTo(tabsView.snp.bottom)
|
|
|
}
|
|
|
|
|
|
- tableView.mj_footer = footer
|
|
|
- tableView.isHidden = true
|
|
|
- tableView.dataSource = self
|
|
|
- tableView.delegate = self
|
|
|
- tableView.separatorStyle = .none
|
|
|
- tableView.showsVerticalScrollIndicator = false
|
|
|
- tableView.showsHorizontalScrollIndicator = false
|
|
|
- tableView.register(
|
|
|
- LNUserSearchItemCell.self,
|
|
|
- forCellReuseIdentifier: LNUserSearchItemCell.className
|
|
|
- )
|
|
|
-
|
|
|
- tableView.addSubview(emptyView)
|
|
|
- emptyView.snp.makeConstraints { make in
|
|
|
- make.centerX.equalToSuperview()
|
|
|
- make.centerY.equalToSuperview().multipliedBy(0.6)
|
|
|
+ let stackView = UIStackView()
|
|
|
+ scrollView.addSubview(stackView)
|
|
|
+ stackView.snp.makeConstraints { make in
|
|
|
+ make.edges.equalToSuperview()
|
|
|
+ make.height.equalToSuperview()
|
|
|
}
|
|
|
|
|
|
- return tableView
|
|
|
- }
|
|
|
-
|
|
|
- private func buildHistory() -> UIView {
|
|
|
- historyView.isHidden = false
|
|
|
- historyView.delegate = self
|
|
|
+ overviewListView.delegate = self
|
|
|
+ for view in resultViews {
|
|
|
+ stackView.addArrangedSubview(view)
|
|
|
+ view.snp.makeConstraints { make in
|
|
|
+ make.width.height.equalTo(scrollView)
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- return historyView
|
|
|
+ return searchView
|
|
|
}
|
|
|
}
|
|
|
|