Files
suixinkan_uikit/suixinkan/UI/LocationReport/Views/LocationReportViews.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

352 lines
12 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.

//
// LocationReportViews.swift
// suixinkan
//
import CoreLocation
import SnapKit
import UIKit
/// /
final class LocationReportMapControlStack: UIView {
var onZoomIn: (() -> Void)?
var onZoomOut: (() -> Void)?
var onRelocate: (() -> Void)?
override init(frame: CGRect) {
super.init(frame: frame)
let zoomIn = makeButton(symbol: "plus", action: #selector(zoomInTapped))
let zoomOut = makeButton(symbol: "minus", action: #selector(zoomOutTapped))
let relocate = makeButton(symbol: "location.fill", action: #selector(relocateTapped))
let zoomStack = UIStackView(arrangedSubviews: [zoomIn, zoomOut])
zoomStack.axis = .vertical
zoomStack.spacing = 0
zoomStack.backgroundColor = .white
zoomStack.layer.cornerRadius = 12
zoomStack.clipsToBounds = true
relocate.backgroundColor = .white
relocate.layer.cornerRadius = 12
let stack = UIStackView(arrangedSubviews: [zoomStack, relocate])
stack.axis = .vertical
stack.spacing = 12
addSubview(stack)
stack.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
[zoomIn, zoomOut, relocate].forEach { button in
button.snp.makeConstraints { make in
make.size.equalTo(44)
}
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func makeButton(symbol: String, action: Selector) -> UIButton {
let button = UIButton(type: .system)
button.setImage(UIImage(systemName: symbol), for: .normal)
button.tintColor = AppColor.primary
button.addTarget(self, action: action, for: .touchUpInside)
return button
}
@objc private func zoomInTapped() { onZoomIn?() }
@objc private func zoomOutTapped() { onZoomOut?() }
@objc private func relocateTapped() { onRelocate?() }
}
///
final class LocationReportStatusCardView: UIView {
var onOnlineStatusTap: (() -> Void)?
var onReminderTap: (() -> Void)?
private let onlineBadge = UILabel()
private let countdownLabel = UILabel()
private let reminderLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
layer.cornerRadius = 10
let statusTitle = makeTitle("当前状态")
onlineBadge.font = .systemFont(ofSize: 12, weight: .medium)
onlineBadge.textAlignment = .center
onlineBadge.layer.cornerRadius = 4
onlineBadge.clipsToBounds = true
onlineBadge.isUserInteractionEnabled = true
onlineBadge.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(onlineTapped)))
let countdownTitle = makeTitle("距离下次上报时间")
countdownLabel.font = .systemFont(ofSize: 16, weight: .medium)
countdownLabel.textColor = AppColor.primary
let reminderTitle = makeTitle("提醒设置")
reminderLabel.font = .systemFont(ofSize: 16, weight: .medium)
reminderLabel.textColor = AppColor.primary
reminderLabel.isUserInteractionEnabled = true
reminderLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(reminderTapped)))
let stack = UIStackView()
stack.axis = .vertical
stack.spacing = 12
stack.addArrangedSubview(makeRow(left: statusTitle, right: onlineBadge))
stack.addArrangedSubview(makeRow(left: countdownTitle, right: countdownLabel))
stack.addArrangedSubview(makeRow(left: reminderTitle, right: reminderLabel))
addSubview(stack)
stack.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(15)
}
onlineBadge.snp.makeConstraints { make in
make.width.greaterThanOrEqualTo(52)
make.height.equalTo(28)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func apply(isOnline: Bool, countdown: String, reminderMinutes: Int) {
onlineBadge.text = isOnline ? "在线" : "离线"
onlineBadge.textColor = isOnline ? AppColor.online : UIColor(hex: 0x7B8EAA)
onlineBadge.backgroundColor = isOnline ? UIColor(hex: 0xF0FDF4) : AppColor.inputBackground
countdownLabel.text = countdown
reminderLabel.text = reminderMinutes == 0 ? "不提醒" : "提前\(reminderMinutes)分钟"
}
@objc private func onlineTapped() { onOnlineStatusTap?() }
@objc private func reminderTapped() { onReminderTap?() }
private func makeTitle(_ text: String) -> UILabel {
let label = UILabel()
label.text = text
label.font = .systemFont(ofSize: 14)
label.textColor = AppColor.textPrimary
return label
}
private func makeRow(left: UIView, right: UIView) -> UIView {
let row = UIStackView(arrangedSubviews: [left, right])
row.axis = .horizontal
row.distribution = .equalSpacing
row.alignment = .center
return row
}
}
///
final class LocationReportActionCardView: UIView {
var onReportTap: (() -> Void)?
private let reportButton = UIButton(type: .system)
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
layer.cornerRadius = 10
let icon = UIImageView(image: UIImage(systemName: "arrow.up"))
icon.tintColor = AppColor.primary
let title = UILabel()
title.text = "立即上报"
title.font = .systemFont(ofSize: 16, weight: .bold)
title.textColor = AppColor.textPrimary
let subtitle = UILabel()
subtitle.text = "您已进入打卡范围"
subtitle.font = .systemFont(ofSize: 12)
subtitle.textColor = AppColor.textTertiary
let textStack = UIStackView(arrangedSubviews: [title, subtitle])
textStack.axis = .vertical
textStack.spacing = 4
reportButton.backgroundColor = AppColor.primary
reportButton.tintColor = .white
reportButton.setImage(UIImage(systemName: "location.fill"), for: .normal)
reportButton.layer.cornerRadius = 36
reportButton.addTarget(self, action: #selector(reportTapped), for: .touchUpInside)
addSubview(icon)
addSubview(textStack)
addSubview(reportButton)
icon.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(15)
make.centerY.equalToSuperview()
make.size.equalTo(24)
}
textStack.snp.makeConstraints { make in
make.leading.equalTo(icon.snp.trailing).offset(8)
make.centerY.equalToSuperview()
}
reportButton.snp.makeConstraints { make in
make.trailing.equalToSuperview().offset(-15)
make.centerY.equalToSuperview()
make.size.equalTo(72)
}
snp.makeConstraints { make in
make.height.equalTo(96)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setReporting(_ reporting: Bool) {
reportButton.isEnabled = !reporting
reportButton.alpha = reporting ? 0.6 : 1
}
@objc private func reportTapped() { onReportTap?() }
}
///
final class LocationReportBottomActionView: UIControl {
private let iconView = UIImageView()
private let titleLabel = UILabel()
init(title: String, symbol: String) {
super.init(frame: .zero)
backgroundColor = .white
layer.cornerRadius = 10
iconView.image = UIImage(systemName: symbol)
iconView.tintColor = AppColor.primary
iconView.contentMode = .scaleAspectFit
titleLabel.text = title
titleLabel.font = .systemFont(ofSize: 12)
titleLabel.textColor = AppColor.textPrimary
titleLabel.textAlignment = .center
let stack = UIStackView(arrangedSubviews: [iconView, titleLabel])
stack.axis = .vertical
stack.spacing = 4
stack.alignment = .center
stack.isUserInteractionEnabled = false
addSubview(stack)
stack.snp.makeConstraints { make in
make.center.equalToSuperview()
}
iconView.snp.makeConstraints { make in
make.size.equalTo(24)
}
snp.makeConstraints { make in
make.height.equalTo(80)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var isSelected: Bool {
didSet { applySelectedStyle() }
}
func applyOnlineStyle(isOnline: Bool) {
if isOnline {
backgroundColor = AppColor.online
iconView.tintColor = .white
titleLabel.text = "在线"
titleLabel.textColor = .white
} else {
backgroundColor = .white
iconView.tintColor = UIColor(hex: 0x7B8EAA)
titleLabel.text = "离线"
titleLabel.textColor = UIColor(hex: 0x7B8EAA)
}
}
private func applySelectedStyle() {
guard titleLabel.text == "标记上报" else { return }
if isSelected {
backgroundColor = AppColor.online
iconView.tintColor = UIColor(hex: 0xF0FDF4)
titleLabel.textColor = UIColor(hex: 0xF0FDF4)
} else {
backgroundColor = .white
iconView.tintColor = AppColor.primary
titleLabel.textColor = AppColor.textPrimary
}
}
}
/// `MAMapView`
final class LocationReportMapView: UIView, MAMapViewDelegate {
var onMapTap: ((CLLocationCoordinate2D) -> Void)?
let mapView: MAMapView
private var markerAnnotation: MAPointAnnotation?
override init(frame: CGRect) {
_ = AMapBootstrap.configureIfNeeded()
mapView = MAMapView(frame: .zero)
super.init(frame: frame)
mapView.delegate = self
mapView.showsUserLocation = true
mapView.showsCompass = false
mapView.zoomLevel = 15
addSubview(mapView)
mapView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func centerOnUserLocation() {
let coordinate = mapView.userLocation.location?.coordinate
guard let coordinate, CLLocationCoordinate2DIsValid(coordinate) else { return }
setCenter(coordinate)
}
func setCenter(_ coordinate: CLLocationCoordinate2D, animated: Bool = true) {
mapView.setCenter(coordinate, animated: animated)
mapView.setZoomLevel(15, animated: animated)
}
func zoomIn() {
mapView.setZoomLevel(mapView.zoomLevel + 1, animated: true)
}
func zoomOut() {
mapView.setZoomLevel(mapView.zoomLevel - 1, animated: true)
}
func updateMarker(latitude: Double?, longitude: Double?) {
if let annotation = markerAnnotation {
mapView.removeAnnotation(annotation)
markerAnnotation = nil
}
guard let latitude, let longitude else { return }
let annotation = MAPointAnnotation()
annotation.coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
markerAnnotation = annotation
mapView.addAnnotation(annotation)
}
func mapView(_ mapView: MAMapView!, didSingleTappedAt coordinate: CLLocationCoordinate2D) {
onMapTap?(coordinate)
}
}