重构排队设置与变更日志交互,补充 Overlay 与资源,并将多页面主操作按钮统一到 UIButton Configuration,同步更新相关单元测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
176 lines
6.6 KiB
Swift
176 lines
6.6 KiB
Swift
//
|
|
// LocationReportViewController.swift
|
|
// suixinkan
|
|
//
|
|
|
|
import CoreLocation
|
|
import SnapKit
|
|
import UIKit
|
|
|
|
/// 位置上报独立页,对齐 Android `LocationReportScreen`。
|
|
final class LocationReportViewController: BaseViewController {
|
|
|
|
private let viewModel: LocationReportViewModel
|
|
private let homeAPI: HomeAPI
|
|
|
|
private let mapContainer = LocationReportMapView()
|
|
private let mapControls = LocationReportMapControlStack()
|
|
private let statusCard = LocationReportStatusCardView()
|
|
private let actionCard = LocationReportActionCardView()
|
|
private let markButton = LocationReportBottomActionView(title: "标记上报", symbol: "mappin.and.ellipse")
|
|
private let historyButton = LocationReportBottomActionView(title: "历史记录", symbol: "clock")
|
|
private let onlineButton = LocationReportBottomActionView(title: "离线", symbol: "power")
|
|
private var hasCenteredMap = false
|
|
|
|
init(
|
|
viewModel: LocationReportViewModel = LocationReportViewModel(),
|
|
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 = "位置上报"
|
|
}
|
|
|
|
override func setupUI() {
|
|
view.backgroundColor = AppColor.pageBackground
|
|
|
|
let bottomStack = UIStackView(arrangedSubviews: [markButton, historyButton, onlineButton])
|
|
bottomStack.axis = .horizontal
|
|
bottomStack.spacing = 12
|
|
bottomStack.distribution = .fillEqually
|
|
|
|
let panelStack = UIStackView(arrangedSubviews: [statusCard, actionCard, bottomStack])
|
|
panelStack.axis = .vertical
|
|
panelStack.spacing = 12
|
|
|
|
view.addSubview(mapContainer)
|
|
view.addSubview(mapControls)
|
|
view.addSubview(panelStack)
|
|
|
|
mapContainer.snp.makeConstraints { make in
|
|
make.top.leading.trailing.equalTo(view.safeAreaLayoutGuide)
|
|
make.height.equalTo(view.snp.height).multipliedBy(0.45)
|
|
}
|
|
mapControls.snp.makeConstraints { make in
|
|
make.trailing.equalTo(mapContainer).offset(-16)
|
|
make.centerY.equalTo(mapContainer)
|
|
}
|
|
panelStack.snp.makeConstraints { make in
|
|
make.top.equalTo(mapContainer.snp.bottom).offset(-20)
|
|
make.leading.trailing.equalToSuperview().inset(15)
|
|
make.bottom.lessThanOrEqualTo(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) }
|
|
}
|
|
|
|
mapControls.onZoomIn = { [weak self] in self?.mapContainer.zoomIn() }
|
|
mapControls.onZoomOut = { [weak self] in self?.mapContainer.zoomOut() }
|
|
mapControls.onRelocate = { [weak self] in
|
|
self?.mapContainer.centerOnUserLocation()
|
|
self?.viewModel.startLocation()
|
|
}
|
|
mapContainer.onMapTap = { [weak self] coordinate in
|
|
self?.viewModel.setMarkerLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
|
|
}
|
|
|
|
statusCard.onOnlineStatusTap = { [weak self] in self?.viewModel.showOnlineStatusSwitchDialog() }
|
|
statusCard.onReminderTap = { [weak self] in
|
|
guard let self else { return }
|
|
LocationReportDialogPresenter.presentReminderPicker(from: self) { minutes in
|
|
self.viewModel.updateReminderMinutes(minutes)
|
|
}
|
|
}
|
|
actionCard.onReportTap = { [weak self] in
|
|
guard let self else { return }
|
|
Task { await self.viewModel.reportLocation(api: self.homeAPI) }
|
|
}
|
|
markButton.addTarget(self, action: #selector(markTapped), for: .touchUpInside)
|
|
historyButton.addTarget(self, action: #selector(historyTapped), for: .touchUpInside)
|
|
onlineButton.addTarget(self, action: #selector(onlineTapped), for: .touchUpInside)
|
|
|
|
viewModel.restoreStateIfNeeded()
|
|
mapContainer.enableInitialUserLocationCentering()
|
|
requestLocationIfNeeded()
|
|
applyViewModel()
|
|
}
|
|
|
|
private func requestLocationIfNeeded() {
|
|
Task {
|
|
do {
|
|
try await LocationProvider.shared.ensureLocationPermission()
|
|
mapContainer.enableInitialUserLocationCentering()
|
|
viewModel.startLocation()
|
|
} catch {
|
|
showToast((error as? LocalizedError)?.errorDescription ?? error.localizedDescription)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func applyViewModel() {
|
|
statusCard.apply(
|
|
isOnline: viewModel.isOnline,
|
|
countdown: viewModel.countdownDisplayText,
|
|
reminderMinutes: viewModel.reminderMinutes
|
|
)
|
|
actionCard.setReporting(viewModel.isReporting)
|
|
markButton.isSelected = viewModel.isMarking
|
|
onlineButton.applyOnlineStyle(isOnline: viewModel.isOnline)
|
|
|
|
mapContainer.updateMarker(latitude: viewModel.markerLatitude, longitude: viewModel.markerLongitude)
|
|
if !hasCenteredMap, let lat = viewModel.currentLatitude, let lng = viewModel.currentLongitude {
|
|
mapContainer.setCenter(CLLocationCoordinate2D(latitude: lat, longitude: lng), animated: false)
|
|
hasCenteredMap = true
|
|
}
|
|
|
|
if viewModel.showOnlineStatusDialog {
|
|
viewModel.hideOnlineStatusSwitchDialog()
|
|
LocationReportDialogPresenter.presentOnlineStatusSwitch(
|
|
from: self,
|
|
isOnline: viewModel.isOnline
|
|
) { [weak self] in
|
|
guard let self else { return }
|
|
Task { await self.viewModel.switchOnlineStatus(api: self.homeAPI) }
|
|
}
|
|
}
|
|
|
|
if viewModel.showLocationReportSuccessDialog {
|
|
let reportTime = viewModel.reportTimeText
|
|
let countdown = viewModel.countdownDisplayText
|
|
viewModel.hideLocationReportSuccessDialog()
|
|
LocationReportDialogPresenter.presentReportSuccess(
|
|
from: self,
|
|
reportTime: reportTime,
|
|
countdown: countdown
|
|
)
|
|
}
|
|
}
|
|
|
|
@objc private func markTapped() {
|
|
viewModel.toggleMarking()
|
|
}
|
|
|
|
@objc private func historyTapped() {
|
|
navigationController?.pushViewController(LocationReportHistoryViewController(), animated: true)
|
|
}
|
|
|
|
@objc private func onlineTapped() {
|
|
viewModel.showOnlineStatusSwitchDialog()
|
|
}
|
|
}
|