添加位置上报地图页和历史列表并对齐 Android

This commit is contained in:
2026-07-07 11:34:54 +08:00
parent 3c25a0b789
commit 3acbf6315b
17 changed files with 1975 additions and 152 deletions

View File

@ -0,0 +1,184 @@
//
// LocationReportHistoryViewController.swift
// suixinkan
//
import SnapKit
import UIKit
/// Android `LocationReportHistoryScreen`
final class LocationReportHistoryViewController: BaseViewController, UITableViewDelegate, UITableViewDataSource {
private let viewModel: LocationReportHistoryViewModel
private let homeAPI: HomeAPI
private let filterBar = LocationReportHistoryFilterBar()
private let tableView = UITableView(frame: .zero, style: .plain)
private let emptyLabel = UILabel()
private let footerLabel = UILabel()
private let refreshControl = UIRefreshControl()
init(
viewModel: LocationReportHistoryViewModel = LocationReportHistoryViewModel(),
homeAPI: HomeAPI = NetworkServices.shared.homeAPI
) {
self.viewModel = viewModel
self.homeAPI = homeAPI
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setupNavigationBar() {
title = "位置上报历史"
}
override func setupUI() {
tableView.delegate = self
tableView.dataSource = self
tableView.separatorStyle = .none
tableView.backgroundColor = AppColor.pageBackground
tableView.register(LocationReportHistoryCell.self, forCellReuseIdentifier: LocationReportHistoryCell.reuseIdentifier)
tableView.refreshControl = refreshControl
emptyLabel.text = "暂无位置上报记录"
emptyLabel.textColor = AppColor.textTertiary
emptyLabel.textAlignment = .center
emptyLabel.isHidden = true
footerLabel.text = "没有更多"
footerLabel.font = .systemFont(ofSize: 13)
footerLabel.textColor = AppColor.textTertiary
footerLabel.textAlignment = .center
footerLabel.isHidden = true
view.addSubview(filterBar)
view.addSubview(tableView)
view.addSubview(emptyLabel)
view.addSubview(footerLabel)
filterBar.snp.makeConstraints { make in
make.top.leading.trailing.equalTo(view.safeAreaLayoutGuide)
}
tableView.snp.makeConstraints { make in
make.top.equalTo(filterBar.snp.bottom)
make.leading.trailing.bottom.equalTo(view.safeAreaLayoutGuide)
}
emptyLabel.snp.makeConstraints { make in
make.center.equalTo(tableView)
}
footerLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-12)
}
}
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) }
}
refreshControl.addTarget(self, action: #selector(refreshTapped), for: .valueChanged)
filterBar.onFilterTap = { [weak self] in self?.presentFilterPicker() }
filterBar.onDateFilterTap = { [weak self] in self?.presentDateFilter() }
filterBar.onClearDates = { [weak self] in
guard let self else { return }
Task { await self.viewModel.clearDateRange(api: self.homeAPI) }
}
Task { await viewModel.loadReportList(api: homeAPI, refresh: true) }
}
private func applyViewModel() {
filterBar.apply(
filterTitle: viewModel.selectedFilter.displayName,
startText: viewModel.startDateText,
endText: viewModel.endDateText,
showsDateRange: viewModel.hasDateFilter
)
tableView.reloadData()
emptyLabel.isHidden = !viewModel.reportList.isEmpty
footerLabel.isHidden = viewModel.reportList.isEmpty || viewModel.canLoadMore
if !viewModel.isRefreshing {
refreshControl.endRefreshing()
}
}
@objc private func refreshTapped() {
Task { await viewModel.loadReportList(api: homeAPI, refresh: true) }
}
private func presentFilterPicker() {
let alert = UIAlertController(title: "筛选类型", message: nil, preferredStyle: .actionSheet)
LocationReportFilterType.allCases.forEach { filter in
alert.addAction(UIAlertAction(title: filter.displayName, style: .default) { [weak self] _ in
guard let self else { return }
Task { await self.viewModel.selectFilter(filter, api: self.homeAPI) }
})
}
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
present(alert, animated: true)
}
private func presentDateFilter() {
let alert = UIAlertController(title: "时间筛选", message: nil, preferredStyle: .actionSheet)
alert.addAction(UIAlertAction(title: "选择开始日期", style: .default) { [weak self] _ in
self?.presentDatePicker(isStart: true)
})
alert.addAction(UIAlertAction(title: "选择结束日期", style: .default) { [weak self] _ in
self?.presentDatePicker(isStart: false)
})
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
present(alert, animated: true)
}
private func presentDatePicker(isStart: Bool) {
let alert = UIAlertController(title: isStart ? "开始日期" : "结束日期", message: "\n\n\n\n\n\n\n\n", preferredStyle: .actionSheet)
let picker = UIDatePicker()
picker.datePickerMode = .date
if #available(iOS 13.4, *) {
picker.preferredDatePickerStyle = .wheels
}
picker.date = isStart ? (viewModel.startDate ?? Date()) : (viewModel.endDate ?? Date())
alert.view.addSubview(picker)
picker.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalToSuperview().offset(24)
}
alert.addAction(UIAlertAction(title: "确定", style: .default) { [weak self] _ in
guard let self else { return }
Task {
if isStart {
await self.viewModel.setDateRange(start: picker.date, end: self.viewModel.endDate, api: self.homeAPI)
} else {
await self.viewModel.setDateRange(start: self.viewModel.startDate, end: picker.date, api: self.homeAPI)
}
}
})
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
present(alert, animated: true)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
viewModel.reportList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(
withIdentifier: LocationReportHistoryCell.reuseIdentifier,
for: indexPath
) as! LocationReportHistoryCell
cell.apply(item: viewModel.reportList[indexPath.row])
return cell
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
Task { await viewModel.loadMoreIfNeeded(currentIndex: indexPath.row, api: homeAPI) }
}
}