重构排队设置与变更日志交互,补充 Overlay 与资源,并将多页面主操作按钮统一到 UIButton Configuration,同步更新相关单元测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
160 lines
5.7 KiB
Swift
160 lines
5.7 KiB
Swift
//
|
|
// ScenicSelectionViewController.swift
|
|
// suixinkan
|
|
//
|
|
|
|
import SnapKit
|
|
import UIKit
|
|
|
|
/// 景区选择页,对齐 Android `ScenicSelectionScreen`。
|
|
final class ScenicSelectionViewController: BaseViewController, UITableViewDataSource, UITableViewDelegate {
|
|
|
|
var onSelectionComplete: (() -> Void)?
|
|
|
|
private let viewModel: ScenicSelectionViewModel
|
|
private let homeAPI: HomeAPI
|
|
|
|
private let bannerView = ScenicPermissionBannerView()
|
|
private let searchBar = ScenicSelectionSearchBar()
|
|
private let locationBar = ScenicCurrentLocationBar()
|
|
private let tableView = UITableView(frame: .zero, style: .plain)
|
|
private let emptyLabel = UILabel()
|
|
|
|
init(viewModel: ScenicSelectionViewModel = ScenicSelectionViewModel(), homeAPI: HomeAPI? = nil) {
|
|
self.viewModel = viewModel
|
|
self.homeAPI = homeAPI ?? NetworkServices.shared.homeAPI
|
|
super.init(nibName: nil, bundle: nil)
|
|
}
|
|
|
|
@available(*, unavailable)
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func setupNavigationBar() {
|
|
title = "景区选择"
|
|
navigationController?.setNavigationBarHidden(false, animated: false)
|
|
}
|
|
|
|
override func setupUI() {
|
|
view.backgroundColor = .white
|
|
|
|
tableView.backgroundColor = .clear
|
|
tableView.separatorStyle = .none
|
|
tableView.dataSource = self
|
|
tableView.delegate = self
|
|
tableView.register(ScenicSpotCell.self, forCellReuseIdentifier: ScenicSpotCell.reuseIdentifier)
|
|
tableView.keyboardDismissMode = .onDrag
|
|
|
|
emptyLabel.text = "暂无景区"
|
|
emptyLabel.font = .systemFont(ofSize: 14)
|
|
emptyLabel.textColor = AppColor.textTertiary
|
|
emptyLabel.textAlignment = .center
|
|
emptyLabel.isHidden = true
|
|
|
|
let contentStack = UIStackView(arrangedSubviews: [searchBar, locationBar])
|
|
contentStack.axis = .vertical
|
|
contentStack.spacing = 16
|
|
contentStack.isLayoutMarginsRelativeArrangement = true
|
|
contentStack.layoutMargins = UIEdgeInsets(top: 0, left: 16, bottom: 16, right: 16)
|
|
|
|
let headerStack = UIStackView(arrangedSubviews: [bannerView, contentStack])
|
|
headerStack.axis = .vertical
|
|
headerStack.spacing = 16
|
|
headerStack.setContentHuggingPriority(.required, for: .vertical)
|
|
headerStack.setContentCompressionResistancePriority(.required, for: .vertical)
|
|
|
|
view.addSubview(headerStack)
|
|
view.addSubview(tableView)
|
|
view.addSubview(emptyLabel)
|
|
|
|
headerStack.snp.makeConstraints { make in
|
|
make.top.equalTo(view.safeAreaLayoutGuide)
|
|
make.leading.trailing.equalToSuperview()
|
|
}
|
|
tableView.snp.makeConstraints { make in
|
|
make.top.equalTo(headerStack.snp.bottom)
|
|
make.leading.trailing.bottom.equalToSuperview()
|
|
}
|
|
emptyLabel.snp.makeConstraints { make in
|
|
make.center.equalTo(tableView)
|
|
}
|
|
}
|
|
|
|
override func bindActions() {
|
|
viewModel.onStateChange = { [weak self] in
|
|
Task { @MainActor in self?.applyViewModel() }
|
|
}
|
|
viewModel.onShowMessage = { [weak self] message in
|
|
Task { @MainActor in self?.showToast(message) }
|
|
}
|
|
viewModel.onSelectionComplete = { [weak self] in
|
|
Task { @MainActor in
|
|
self?.onSelectionComplete?()
|
|
self?.navigationController?.popViewController(animated: true)
|
|
}
|
|
}
|
|
viewModel.onNavigateApply = { [weak self] in
|
|
Task { @MainActor in self?.pushPermissionApply() }
|
|
}
|
|
viewModel.onNavigateApplyStatus = { [weak self] applyCode in
|
|
Task { @MainActor in self?.pushPermissionApplyStatus(applyCode: applyCode) }
|
|
}
|
|
|
|
bannerView.onApplyNow = { [weak self] in
|
|
guard let self else { return }
|
|
Task { await self.viewModel.onApplyNow(api: self.homeAPI) }
|
|
}
|
|
searchBar.onSearchTextChange = { [weak self] query in
|
|
self?.viewModel.onSearchQueryChange(query)
|
|
}
|
|
locationBar.onRelocate = { [weak self] in
|
|
guard let self else { return }
|
|
Task { await self.viewModel.startLocation() }
|
|
}
|
|
|
|
viewModel.loadFromLocal()
|
|
Task { await viewModel.startLocation() }
|
|
}
|
|
|
|
private func applyViewModel() {
|
|
locationBar.apply(locationText: viewModel.currentLocationText, isLocating: viewModel.isLocating)
|
|
emptyLabel.isHidden = !viewModel.spots.isEmpty
|
|
tableView.reloadData()
|
|
if viewModel.isLoading {
|
|
showLoading()
|
|
} else {
|
|
hideLoading()
|
|
}
|
|
}
|
|
|
|
private func pushPermissionApply(pendingData: RoleApplyPendingResponse? = nil) {
|
|
let controller = PermissionApplyViewController(pendingData: pendingData)
|
|
navigationController?.pushViewController(controller, animated: true)
|
|
}
|
|
|
|
private func pushPermissionApplyStatus(applyCode: String) {
|
|
let controller = PermissionApplyStatusViewController(applyCode: applyCode)
|
|
navigationController?.pushViewController(controller, animated: true)
|
|
}
|
|
|
|
// MARK: - UITableView
|
|
|
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
viewModel.spots.count
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
let cell = tableView.dequeueReusableCell(
|
|
withIdentifier: ScenicSpotCell.reuseIdentifier,
|
|
for: indexPath
|
|
) as! ScenicSpotCell
|
|
let spot = viewModel.spots[indexPath.row]
|
|
cell.apply(spot)
|
|
cell.onTap = { [weak self] in
|
|
self?.viewModel.selectSpot(spot)
|
|
}
|
|
return cell
|
|
}
|
|
}
|