Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import SwiftUI
import Observation
import struct Yosemite.Country

@available(iOS 17, *)
struct AddressMapPickerView: View {
@Environment(\.dismiss) private var dismiss
@State private var viewModel: AddressMapPickerViewModel
Expand All @@ -18,12 +17,15 @@ struct AddressMapPickerView: View {
var body: some View {
NavigationStack {
ZStack(alignment: .top) {
Map(coordinateRegion: $viewModel.region,
showsUserLocation: true,
annotationItems: viewModel.annotations) { item in
MapMarker(coordinate: item.coordinate)
Map(position: $viewModel.mapPosition) {
ForEach(viewModel.annotations) { item in
Marker(coordinate: item.coordinate) {
RoundedRectangle(cornerRadius: 5)
}
}
}
.ignoresSafeArea()
.mapStyle(.standard(elevation: .realistic))
.ignoresSafeArea()

VStack(spacing: 0) {
searchBar
Expand Down Expand Up @@ -118,6 +120,12 @@ struct AddressMapPickerView: View {
.autocorrectionDisabled()
.textInputAutocapitalization(.never)
.focused($isSearchFocused)
.onChange(of: isSearchFocused) { _, newValue in
viewModel.isSearchFocused = newValue
}
.onChange(of: viewModel.isSearchFocused) { _, newValue in
isSearchFocused = newValue
}

if !viewModel.searchQuery.isEmpty {
Button(action: {
Expand All @@ -134,7 +142,6 @@ struct AddressMapPickerView: View {
}
}

@available(iOS 17, *)
private extension AddressMapPickerView {
enum Localization {
static let close = NSLocalizedString("addressMapPicker.button.close", value: "Close", comment: "Text for the close button in the Edit Address Form.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,20 @@ final class DefaultAddressMapLocalSearchProvider: AddressMapLocalSearchProviding
}
}

@available(iOS 17, *)
@Observable
final class AddressMapPickerViewModel: NSObject {
var searchQuery = "" {
willSet {
searchQueryContinuation.yield(newValue)
}
}
// Syncs with the focused state of the search text field in `AddressMapPickerView`.
var isSearchFocused: Bool = false
private(set) var searchResults: [MKLocalSearchCompletion] = []
var region = MKCoordinateRegion(
var mapPosition: MapCameraPosition = .region(MKCoordinateRegion(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious why are we using Cupertino coordinates here? Should we pass in some sort of Location service that uses CLLocationManagerDelegate for the merchant's location? (I see we already instantiate a CLLocationManager when configuring the map) Or not really needed?

center: CLLocationCoordinate2D(latitude: 37.3361, longitude: -122.0380),
span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
)
))
var annotations: [MapAnnotation] = []
var showsSearchResults: Bool {
searchResults.isEmpty == false
Expand Down Expand Up @@ -79,7 +80,12 @@ final class AddressMapPickerViewModel: NSObject {
for await query in searchQueryStream.debounce(for: .seconds(0.3)) {
if query.isEmpty {
searchResults = []
} else {
} else if isSearchFocused {
if let region = mapPosition.region {
// ~11km centered in the map region.
searchCompleter.region = .init(center: .init(latitude: region.center.latitude, longitude: region.center.longitude),
span: .init(latitudeDelta: 0.1, longitudeDelta: 0.1))
}
searchCompleter.queryFragment = query
}
}
Expand Down Expand Up @@ -124,7 +130,6 @@ final class AddressMapPickerViewModel: NSObject {
}
}

@available(iOS 17, *)
private extension AddressMapPickerViewModel {
func configureSearchCompleter() {
searchCompleter.resultTypes = .address
Expand All @@ -140,10 +145,10 @@ private extension AddressMapPickerViewModel {
let currentLocation = locationManager.location {
DispatchQueue.main.async { [weak self] in
guard let self else { return }
region = MKCoordinateRegion(
mapPosition = .region(MKCoordinateRegion(
center: currentLocation.coordinate,
span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)
)
))
}
return
}
Expand All @@ -159,10 +164,10 @@ private extension AddressMapPickerViewModel {
return
}

region = MKCoordinateRegion(
mapPosition = .region(MKCoordinateRegion(
center: location.coordinate,
span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)
)
))
annotations = [MapAnnotation(coordinate: location.coordinate)]
} catch {
DDLogError("⛔️ Error geocoding initial address: \(error)")
Expand All @@ -173,20 +178,13 @@ private extension AddressMapPickerViewModel {

@MainActor
func onSelectedPlacemark(_ placemark: MKPlacemark) async {
await withCheckedContinuation { continuation in
withAnimation { [weak self] in
guard let self else { return }
searchResults = []
region = MKCoordinateRegion(
center: placemark.coordinate,
span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)
)
annotations = [MapAnnotation(coordinate: placemark.coordinate)]
selectedPlace = placemark
} completion: {
continuation.resume()
}
}
searchResults = []
mapPosition = .region(MKCoordinateRegion(
center: placemark.coordinate,
span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)
))
annotations = [MapAnnotation(coordinate: placemark.coordinate)]
selectedPlace = placemark
}

func formatAddressString(fields: AddressFormFields) -> String {
Expand All @@ -196,7 +194,6 @@ private extension AddressMapPickerViewModel {
}
}

@available(iOS 17, *)
extension AddressMapPickerViewModel: MKLocalSearchCompleterDelegate {
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
searchResults = completer.results
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ struct AddressMapPickerViewModelTests {

// MARK: - Initialization Tests

@available(iOS 17, *)
@Test func initialization_with_empty_fields_sets_properties_with_default_values() {
// Given
let emptyFields = AddressFormFields()
Expand All @@ -38,7 +37,6 @@ struct AddressMapPickerViewModelTests {

// MARK: - Selection Tests

@available(iOS 17, *)
@Test func selectLocation_updates_annotations_and_hasValidSelection() async {
// Given
let fields = AddressFormFields()
Expand All @@ -56,7 +54,6 @@ struct AddressMapPickerViewModelTests {

// MARK: - Address Field Updates Tests

@available(iOS 17, *)
@Test func updateFields_with_no_selected_place_does_not_modify_fields() {
// Given
let sut = AddressMapPickerViewModel(fields: .init(), countryByCode: mockCountryByCode)
Expand All @@ -72,7 +69,6 @@ struct AddressMapPickerViewModelTests {
#expect(updatedFields.city == "Original City")
}

@available(iOS 17, *)
@Test func updateFields_when_country_not_found_in_countryByCode_sets_country_and_state_as_strings() async {
// Given
let mockSearchProvider = MockAddressMapLocalSearchProvider.withFrenchAddress()
Expand All @@ -95,7 +91,6 @@ struct AddressMapPickerViewModelTests {
#expect(updatedFields.selectedState == nil)
}

@available(iOS 17, *)
@Test func updateFields_when_country_is_found_in_countryByCode_sets_selected_country_and_state() async {
// Given
let mockSearchProvider = MockAddressMapLocalSearchProvider.withUSAddress()
Expand Down
Loading