Files
Luma/Sources/Shared/UI/LocationPickerView.swift
T
2026-07-30 20:07:51 +07:00

149 lines
5.4 KiB
Swift

import CoreLocation
import Combine
import Foundation
import MapKit
import SwiftUI
@MainActor
struct LocationPickerView: View {
@Environment(\.dismiss) private var dismiss
@StateObject private var locationProvider = LocationProvider()
@State private var cameraPosition: MapCameraPosition = .automatic
@State private var selectedCoordinate: CLLocationCoordinate2D?
let onSend: (GeoLocation) -> Void
var body: some View {
NavigationStack {
ZStack {
Map(position: $cameraPosition) {
UserAnnotation()
}
.mapStyle(.standard(elevation: .realistic))
.onMapCameraChange(frequency: .onEnd) { context in
selectedCoordinate = context.region.center
}
Image(systemName: "mappin.circle.fill")
.font(.system(size: 42, weight: .semibold))
.symbolRenderingMode(.palette)
.foregroundStyle(.white, Color.accentColor)
.shadow(color: .black.opacity(0.28), radius: 4, y: 2)
.offset(y: -20)
.allowsHitTesting(false)
VStack {
if let message = locationProvider.errorMessage {
Text(message)
.font(.caption)
.multilineTextAlignment(.center)
.padding(.horizontal, 12)
.padding(.vertical, 8)
.background(.regularMaterial, in: Capsule())
.padding()
}
Spacer()
HStack {
Spacer()
Button(action: centerOnCurrentLocation) {
Group {
if locationProvider.isLocating {
ProgressView()
} else {
Image(systemName: "location.fill")
}
}
.frame(width: 44, height: 44)
.background(.regularMaterial, in: Circle())
.shadow(color: .black.opacity(0.16), radius: 5, y: 2)
}
.buttonStyle(.plain)
.accessibilityLabel("Показать мою геопозицию")
}
.padding(.horizontal, 16)
.padding(.bottom, 10)
}
}
.safeAreaInset(edge: .bottom) {
VStack(spacing: 10) {
Text(coordinateText)
.font(.caption.monospacedDigit())
.foregroundStyle(.secondary)
Button(action: sendSelection) {
Label("Отправить эту геопозицию", systemImage: "location.fill")
.font(.headline)
.foregroundStyle(.white)
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
.background(Color.accentColor, in: RoundedRectangle(cornerRadius: 14))
}
.buttonStyle(.plain)
.disabled(selectedCoordinate == nil)
}
.padding(16)
.background(.regularMaterial)
}
.navigationTitle("Геопозиция")
#if os(iOS)
.navigationBarTitleDisplayMode(.inline)
#endif
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Отмена") { dismiss() }
}
}
}
.onAppear {
locationProvider.requestCurrentLocation()
}
.onReceive(locationProvider.$location.compactMap { $0 }) { location in
selectedCoordinate = location.coordinate
cameraPosition = .region(
MKCoordinateRegion(
center: location.coordinate,
latitudinalMeters: 1_200,
longitudinalMeters: 1_200
)
)
}
}
private var coordinateText: String {
guard let selectedCoordinate else { return "Переместите карту, чтобы выбрать точку" }
return String(
format: "%.6f, %.6f",
locale: Locale(identifier: "en_US_POSIX"),
selectedCoordinate.latitude,
selectedCoordinate.longitude
)
}
private func centerOnCurrentLocation() {
if let location = locationProvider.location {
selectedCoordinate = location.coordinate
cameraPosition = .region(
MKCoordinateRegion(
center: location.coordinate,
latitudinalMeters: 1_200,
longitudinalMeters: 1_200
)
)
} else {
locationProvider.requestCurrentLocation()
}
}
private func sendSelection() {
guard let selectedCoordinate,
let location = GeoLocation(
latitude: selectedCoordinate.latitude,
longitude: selectedCoordinate.longitude
) else { return }
onSend(location)
dismiss()
}
}