Files
suixinkan_uikit/suixinkan/UI/LocationReport/LocationReportViewController.swift
汉秋 cfcd3eee6f Integrate Amap SDK to replace CoreLocation and MapKit for all location features.
Unify GPS, reverse geocoding, and map display behind LocationProvider with privacy-gated bootstrap after login, aligned with Android.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 11:49:22 +08:00

174 lines
6.5 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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() {
Task {
do {
try await LocationProvider.shared.ensureLocationPermission()
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()
}
}