Files
suixinkan_uikit/suixinkan/UI/LocationReport/LocationReportViewController.swift
汉秋 9899a2ccb3 Fix location report parsing and center map on user location immediately.
Decode numeric staff_id from the report API and auto-center the map when the first user location update arrives.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 12:01:46 +08:00

176 lines
6.6 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?.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()
}
}