From 25af9ff31a27c6142673859bfee385a4f7f99dbf Mon Sep 17 00:00:00 2001 From: Alfonso Grillo Date: Wed, 12 Jul 2023 11:02:05 +0200 Subject: [PATCH] Send location uncertainty within a geo uri (#1309) * Add uncertainty * Add UTs * Delete verticalAccuracy --- .../Other/MapLibre/MapLibreMapView.swift | 29 ++++++++++++++----- .../LocationSharingScreenModels.swift | 1 + .../StaticLocationScreenViewModel.swift | 3 +- .../View/StaticLocationScreen.swift | 4 ++- .../Sources/Services/Timeline/GeoURI.swift | 4 +-- .../StaticLocationScreenViewModelTests.swift | 27 +++++++++++++++++ 6 files changed, 57 insertions(+), 11 deletions(-) diff --git a/ElementX/Sources/Other/MapLibre/MapLibreMapView.swift b/ElementX/Sources/Other/MapLibre/MapLibreMapView.swift index 41a039f8b..3f5309cb9 100644 --- a/ElementX/Sources/Other/MapLibre/MapLibreMapView.swift +++ b/ElementX/Sources/Other/MapLibre/MapLibreMapView.swift @@ -57,6 +57,9 @@ struct MapLibreMapView: UIViewRepresentable { @Binding var mapCenterCoordinate: CLLocationCoordinate2D? @Binding var isLocationAuthorized: Bool? + + // The radius of uncertainty for the location, measured in meters. + @Binding var geolocationUncertainty: CLLocationAccuracy? /// Called when the user pan on the map var userDidPan: (() -> Void)? @@ -155,6 +158,7 @@ extension MapLibreMapView { } previousUserLocation = userLocation + updateGeolocationUncertainty(location: userLocation) } func mapView(_ mapView: MGLMapView, didChangeLocationManagerAuthorization manager: MGLLocationManager) { @@ -176,13 +180,7 @@ extension MapLibreMapView { mapLibreView.mapCenterCoordinate = mapView.centerCoordinate } } - - // MARK: Callout - - func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool { - false - } - + func mapView(_ mapView: MGLMapView, shouldChangeFrom oldCamera: MGLMapCamera, to newCamera: MGLMapCamera, reason: MGLCameraChangeReason) -> Bool { // we send the userDidPan event only for the reasons that actually will change the map center, and not zoom only / rotations only events. switch reason { @@ -203,6 +201,23 @@ extension MapLibreMapView { } return true } + + // MARK: Callout + + func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool { + false + } + + // MARK: Private + + private func updateGeolocationUncertainty(location: MGLUserLocation) { + guard let clLocation = location.location, clLocation.horizontalAccuracy >= 0 else { + mapLibreView.geolocationUncertainty = nil + return + } + + mapLibreView.geolocationUncertainty = clLocation.horizontalAccuracy + } } } diff --git a/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenModels.swift b/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenModels.swift index 35dd1291a..eeea4973c 100644 --- a/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenModels.swift +++ b/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenModels.swift @@ -109,6 +109,7 @@ struct StaticLocationScreenViewState: BindableState { struct StaticLocationScreenBindings { var mapCenterLocation: CLLocationCoordinate2D? + var geolocationUncertainty: CLLocationAccuracy? var showsUserLocationMode: ShowUserLocationMode diff --git a/ElementX/Sources/Screens/LocationSharing/StaticLocationScreenViewModel.swift b/ElementX/Sources/Screens/LocationSharing/StaticLocationScreenViewModel.swift index bef056726..426f850a6 100644 --- a/ElementX/Sources/Screens/LocationSharing/StaticLocationScreenViewModel.swift +++ b/ElementX/Sources/Screens/LocationSharing/StaticLocationScreenViewModel.swift @@ -36,7 +36,8 @@ class StaticLocationScreenViewModel: StaticLocationScreenViewModelType, StaticLo actionsSubject.send(.close) case .selectLocation: guard let coordinate = state.bindings.mapCenterLocation else { return } - actionsSubject.send(.sendLocation(.init(coordinate: coordinate), isUserLocation: state.isSharingUserLocation)) + let uncertainty = state.isSharingUserLocation ? context.geolocationUncertainty : nil + actionsSubject.send(.sendLocation(.init(coordinate: coordinate, uncertainty: uncertainty), isUserLocation: state.isSharingUserLocation)) case .userDidPan: state.bindings.showsUserLocationMode = .show case .centerToUser: diff --git a/ElementX/Sources/Screens/LocationSharing/View/StaticLocationScreen.swift b/ElementX/Sources/Screens/LocationSharing/View/StaticLocationScreen.swift index 2c2d2973e..1734c2fa9 100644 --- a/ElementX/Sources/Screens/LocationSharing/View/StaticLocationScreen.swift +++ b/ElementX/Sources/Screens/LocationSharing/View/StaticLocationScreen.swift @@ -47,10 +47,12 @@ struct StaticLocationScreen: View { showsUserLocationMode: $context.showsUserLocationMode, error: $context.mapError, mapCenterCoordinate: $context.mapCenterLocation, - isLocationAuthorized: $context.isLocationAuthorized) { + isLocationAuthorized: $context.isLocationAuthorized, + geolocationUncertainty: $context.geolocationUncertainty) { context.send(viewAction: .userDidPan) } .ignoresSafeArea(.all, edges: mapSafeAreaEdges) + if context.viewState.isLocationPickerMode { LocationMarkerView() } diff --git a/ElementX/Sources/Services/Timeline/GeoURI.swift b/ElementX/Sources/Services/Timeline/GeoURI.swift index 997d17b83..e57e918f6 100644 --- a/ElementX/Sources/Services/Timeline/GeoURI.swift +++ b/ElementX/Sources/Services/Timeline/GeoURI.swift @@ -78,8 +78,8 @@ private extension RegexGeoURI { } extension GeoURI { - init(coordinate: CLLocationCoordinate2D) { - self.init(latitude: coordinate.latitude, longitude: coordinate.longitude) + init(coordinate: CLLocationCoordinate2D, uncertainty: CLLocationAccuracy?) { + self.init(latitude: coordinate.latitude, longitude: coordinate.longitude, uncertainty: uncertainty) } } diff --git a/UnitTests/Sources/StaticLocationScreenViewModelTests.swift b/UnitTests/Sources/StaticLocationScreenViewModelTests.swift index 381a487b0..9b6d66323 100644 --- a/UnitTests/Sources/StaticLocationScreenViewModelTests.swift +++ b/UnitTests/Sources/StaticLocationScreenViewModelTests.swift @@ -76,4 +76,31 @@ class StaticLocationScreenViewModelTests: XCTestCase { let authorizationError = AlertInfo(locationSharingViewError: .missingAuthorization) XCTAssertEqual(authorizationError.message, L10n.errorMissingLocationAuth(InfoPlistReader.main.bundleDisplayName)) } + + func testSendUserLocation() async throws { + context.mapCenterLocation = .init(latitude: 0, longitude: 0) + context.geolocationUncertainty = 10 + let deferred = deferFulfillment(viewModel.actions.first()) + context.send(viewAction: .selectLocation) + guard case .sendLocation(let geoUri, let isUserLocation) = try await deferred.fulfill() else { + XCTFail("Sent action should be 'sendLocation'") + return + } + XCTAssertEqual(geoUri.uncertainty, 10) + XCTAssertTrue(isUserLocation) + } + + func testSendPickedLocation() async throws { + context.mapCenterLocation = .init(latitude: 0, longitude: 0) + context.isLocationAuthorized = nil + context.geolocationUncertainty = 10 + let deferred = deferFulfillment(viewModel.actions.first()) + context.send(viewAction: .selectLocation) + guard case .sendLocation(let geoUri, let isUserLocation) = try await deferred.fulfill() else { + XCTFail("Sent action should be 'sendLocation'") + return + } + XCTAssertEqual(geoUri.uncertainty, nil) + XCTAssertFalse(isUserLocation) + } }