添加位置上报地图页和历史列表并对齐 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,177 @@
//
// 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 = 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() {
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?.viewModel.startLocation()
self?.mapContainer.centerOnUserLocation()
}
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()
requestLocationIfNeeded()
applyViewModel()
}
private func requestLocationIfNeeded() {
let status = CLLocationManager.authorizationStatus()
switch status {
case .authorizedAlways, .authorizedWhenInUse:
viewModel.startLocation()
case .notDetermined:
CLLocationManager().requestWhenInUseAuthorization()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
self?.viewModel.startLocation()
}
default:
showToast("需要定位权限才能使用此功能")
}
}
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()
}
}