diff --git a/Riot/Modules/LocationSharing/LocationManager.swift b/Riot/Modules/LocationSharing/LocationManager.swift index 857a59597..d51bdcac4 100644 --- a/Riot/Modules/LocationSharing/LocationManager.swift +++ b/Riot/Modules/LocationSharing/LocationManager.swift @@ -226,3 +226,19 @@ extension LocationManager: CLLocationManagerDelegate { MXLog.error("[LocationManager] Did failed", context: error) } } + +extension CLLocationManager { + func requestAuthorizationIfNeeded() -> Bool { + switch authorizationStatus { + case .notDetermined: + requestWhenInUseAuthorization() + return false + case .restricted, .denied: + return false + case .authorizedAlways, .authorizedWhenInUse, .authorized: + return true + @unknown default: + return false + } + } +} diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerModels.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerModels.swift index 5c4f8a841..e6e93ccca 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerModels.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerModels.swift @@ -42,6 +42,12 @@ struct LiveLocationSharingViewerViewState: BindableState { /// Live location list items var listItemsViewData: [LiveLocationListItemViewData] + var showsUserLocation = false + + var isCurrentUserShared: Bool { + listItemsViewData.contains { $0.isCurrentUser } + } + var showLoadingIndicator = false var shareButtonEnabled: Bool { @@ -75,4 +81,5 @@ enum LiveLocationSharingViewerViewAction { case tapListItem(_ userId: String) case share(_ annotation: UserLocationAnnotation) case mapCreditsDidTap + case showUserLocation } diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerViewModel.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerViewModel.swift index ebfd06220..6cc99e5a0 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerViewModel.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerViewModel.swift @@ -72,6 +72,8 @@ class LiveLocationSharingViewerViewModel: LiveLocationSharingViewerViewModelType completion?(.share(userLocationAnnotation.coordinate)) case .mapCreditsDidTap: state.bindings.showMapCreditsSheet.toggle() + case .showUserLocation: + showsCurrentUserLocation() } } @@ -229,4 +231,12 @@ class LiveLocationSharingViewerViewModel: LiveLocationSharingViewerViewModelType } } } + + private func showsCurrentUserLocation() { + if liveLocationSharingViewerService.requestAuthorizationIfNeeded() { + state.showsUserLocation = true + } else { + state.errorSubject.send(.invalidLocationAuthorization) + } + } } diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/LiveLocationSharingViewerServiceProtocol.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/LiveLocationSharingViewerServiceProtocol.swift index cb9e107a5..864fa080f 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/LiveLocationSharingViewerServiceProtocol.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/LiveLocationSharingViewerServiceProtocol.swift @@ -33,4 +33,6 @@ protocol LiveLocationSharingViewerServiceProtocol { /// Stop current user location sharing func stopUserLiveLocationSharing(completion: @escaping (Result) -> Void) + + func requestAuthorizationIfNeeded() -> Bool } diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/MatrixSDK/LiveLocationSharingViewerService.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/MatrixSDK/LiveLocationSharingViewerService.swift index 9b646a1bf..58c07c942 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/MatrixSDK/LiveLocationSharingViewerService.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/MatrixSDK/LiveLocationSharingViewerService.swift @@ -19,11 +19,13 @@ import Foundation import MatrixSDK class LiveLocationSharingViewerService: LiveLocationSharingViewerServiceProtocol { + // MARK: - Properties private(set) var usersLiveLocation: [UserLiveLocation] = [] private let roomId: String private var beaconInfoSummaryListener: Any? + private let locationManager = CLLocationManager() // MARK: Private @@ -74,6 +76,10 @@ class LiveLocationSharingViewerService: LiveLocationSharingViewerServiceProtocol } } + func requestAuthorizationIfNeeded() -> Bool { + locationManager.requestAuthorizationIfNeeded() + } + // MARK: - Private private func updateUsersLiveLocation(notifyUpdate: Bool) { diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/Mock/MockLiveLocationSharingViewerService.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/Mock/MockLiveLocationSharingViewerService.swift index cfb34ef04..64b32dfb2 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/Mock/MockLiveLocationSharingViewerService.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/Mock/MockLiveLocationSharingViewerService.swift @@ -27,12 +27,17 @@ class MockLiveLocationSharingViewerService: LiveLocationSharingViewerServiceProt // MARK: Setup - init(generateRandomUsers: Bool = false) { - let firstUserLiveLocation = createFirstUserLiveLocation() + init(generateRandomUsers: Bool = false, currentUserSharingLocation: Bool = true) { + let firstUserLiveLocation: UserLiveLocation? + if currentUserSharingLocation { + firstUserLiveLocation = createFirstUserLiveLocation() + } else { + firstUserLiveLocation = nil + } let secondUserLiveLocation = createSecondUserLiveLocation() - var usersLiveLocation: [UserLiveLocation] = [firstUserLiveLocation, secondUserLiveLocation] + var usersLiveLocation: [UserLiveLocation] = [firstUserLiveLocation, secondUserLiveLocation].compactMap { $0 } if generateRandomUsers { for _ in 1...20 { @@ -56,6 +61,10 @@ class MockLiveLocationSharingViewerService: LiveLocationSharingViewerServiceProt func stopUserLiveLocationSharing(completion: @escaping (Result) -> Void) { } + func requestAuthorizationIfNeeded() -> Bool { + return true + } + // MARK: Private private func createFirstUserLiveLocation() -> UserLiveLocation { diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Test/Unit/LiveLocationSharingViewerViewModelTests.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Test/Unit/LiveLocationSharingViewerViewModelTests.swift index cefe1c2ed..37ee71a61 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Test/Unit/LiveLocationSharingViewerViewModelTests.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Test/Unit/LiveLocationSharingViewerViewModelTests.swift @@ -15,6 +15,7 @@ // import Combine +import CoreLocation import XCTest @testable import RiotSwiftUI @@ -30,4 +31,17 @@ class LiveLocationSharingViewerViewModelTests: XCTestCase { viewModel = LiveLocationSharingViewerViewModel(mapStyleURL: BuildSettings.defaultTileServerMapStyleURL, service: service) context = viewModel.context } + + func testIsUserBeingShared() { + XCTAssertTrue(context.viewState.isCurrentUserShared) + } + + func testToggleShowUserLocation() { + let service = MockLiveLocationSharingViewerService(currentUserSharingLocation: false) + let viewModel = LiveLocationSharingViewerViewModel(mapStyleURL: BuildSettings.defaultTileServerMapStyleURL, service: service) + XCTAssertFalse(viewModel.context.viewState.isCurrentUserShared) + XCTAssertFalse(viewModel.context.viewState.showsUserLocation) + viewModel.context.send(viewAction: .showUserLocation) + XCTAssertTrue(viewModel.context.viewState.showsUserLocation) + } } diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/View/LiveLocationSharingViewer.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/View/LiveLocationSharingViewer.swift index 9678028b8..2e1e1c012 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/View/LiveLocationSharingViewer.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/View/LiveLocationSharingViewer.swift @@ -34,23 +34,35 @@ struct LiveLocationSharingViewer: View { @ObservedObject var viewModel: LiveLocationSharingViewerViewModel.Context + var mapView: LocationSharingMapView { + LocationSharingMapView(tileServerMapURL: viewModel.viewState.mapStyleURL, + annotations: viewModel.viewState.annotations, + highlightedAnnotation: viewModel.viewState.highlightedAnnotation, + userAvatarData: nil, + showsUserLocation: viewModel.viewState.showsUserLocation, + userAnnotationCanShowCallout: true, + userLocation: Binding.constant(nil), + mapCenterCoordinate: Binding.constant(nil), + onCalloutTap: { annotation in + if let userLocationAnnotation = annotation as? UserLocationAnnotation { + viewModel.send(viewAction: .share(userLocationAnnotation)) + } + }, + errorSubject: viewModel.viewState.errorSubject) + } + var body: some View { ZStack(alignment: .bottom) { if !viewModel.viewState.showMapLoadingError { - LocationSharingMapView(tileServerMapURL: viewModel.viewState.mapStyleURL, - annotations: viewModel.viewState.annotations, - highlightedAnnotation: viewModel.viewState.highlightedAnnotation, - userAvatarData: nil, - showsUserLocation: false, - userAnnotationCanShowCallout: true, - userLocation: Binding.constant(nil), - mapCenterCoordinate: Binding.constant(nil), - onCalloutTap: { annotation in - if let userLocationAnnotation = annotation as? UserLocationAnnotation { - viewModel.send(viewAction: .share(userLocationAnnotation)) - } - }, - errorSubject: viewModel.viewState.errorSubject) + + if !viewModel.viewState.isCurrentUserShared { + mapView + .overlay(CenterToUserLocationButton(action: { + viewModel.send(viewAction: .showUserLocation) + }).offset(x: -11.0, y: 52), alignment: .topTrailing) + } else { + mapView + } // Show map credits above collapsed bottom sheet height if bottom sheet is visible if viewModel.viewState.isBottomSheetVisible { @@ -178,3 +190,27 @@ struct LiveLocationSharingViewer_Previews: PreviewProvider { } } } + +struct CenterToUserLocationButton: View { + + // MARK: Private + + @Environment(\.theme) private var theme: ThemeSwiftUI + + // MARK: Public + + var action: () -> Void + + var body: some View { + Button { + action() + } label: { + Image(uiImage: Asset.Images.locationCenterMapIcon.image) + .foregroundColor(theme.colors.accent) + } + .padding(8.0) + .background(theme.colors.background) + .clipShape(Circle()) + .shadow(radius: 2.0) + } +} diff --git a/RiotSwiftUI/Modules/LocationSharing/MapError/MapViewErrorAlertInfoBuilder.swift b/RiotSwiftUI/Modules/LocationSharing/MapError/MapViewErrorAlertInfoBuilder.swift index fa02eef50..fe7ad1521 100644 --- a/RiotSwiftUI/Modules/LocationSharing/MapError/MapViewErrorAlertInfoBuilder.swift +++ b/RiotSwiftUI/Modules/LocationSharing/MapError/MapViewErrorAlertInfoBuilder.swift @@ -32,7 +32,7 @@ struct MapViewErrorAlertInfoBuilder { case .invalidLocationAuthorization: alertInfo = AlertInfo(id: .authorizationError, title: VectorL10n.locationSharingInvalidAuthorizationErrorTitle(AppInfo.current.displayName), - primaryButton: (VectorL10n.locationSharingInvalidAuthorizationNotNow, primaryButtonCompletion), + primaryButton: (VectorL10n.locationSharingInvalidAuthorizationNotNow, {}), secondaryButton: (VectorL10n.locationSharingInvalidAuthorizationSettings, primaryButtonCompletion)) default: alertInfo = nil diff --git a/RiotSwiftUI/Modules/LocationSharing/MapView/View/LocationSharingMapView.swift b/RiotSwiftUI/Modules/LocationSharing/MapView/View/LocationSharingMapView.swift index 9b5073b33..31165396d 100644 --- a/RiotSwiftUI/Modules/LocationSharing/MapView/View/LocationSharingMapView.swift +++ b/RiotSwiftUI/Modules/LocationSharing/MapView/View/LocationSharingMapView.swift @@ -75,7 +75,7 @@ struct LocationSharingMapView: UIViewRepresentable { mapView.vc_removeAllAnnotations() mapView.addAnnotations(annotations) - if let highlightedAnnotation = highlightedAnnotation { + if let highlightedAnnotation = highlightedAnnotation, !showsUserLocation { mapView.setCenter(highlightedAnnotation.coordinate, zoomLevel: Constants.mapZoomLevel, animated: false) } @@ -125,11 +125,14 @@ extension LocationSharingMapView { return LocationAnnotationView(userLocationAnnotation: userLocationAnnotation) } else if let pinLocationAnnotation = annotation as? PinLocationAnnotation { return LocationAnnotationView(pinLocationAnnotation: pinLocationAnnotation) - } else if annotation is MGLUserLocation, let currentUserAvatarData = locationSharingMapView.userAvatarData { - // Replace default current location annotation view with a UserLocationAnnotatonView when the map is center on user location - return LocationAnnotationView(avatarData: currentUserAvatarData) + } else if annotation is MGLUserLocation { + if let currentUserAvatarData = locationSharingMapView.userAvatarData { + // Replace default current location annotation view with a UserLocationAnnotatonView when the map is center on user location + return LocationAnnotationView(avatarData: currentUserAvatarData) + } else { + return LocationAnnotationView(userPinLocationAnnotation: annotation) + } } - return nil } diff --git a/RiotSwiftUI/Modules/LocationSharing/MapView/View/UserLocationAnnotationView.swift b/RiotSwiftUI/Modules/LocationSharing/MapView/View/UserLocationAnnotationView.swift index 25a5b9848..0770f9b2c 100644 --- a/RiotSwiftUI/Modules/LocationSharing/MapView/View/UserLocationAnnotationView.swift +++ b/RiotSwiftUI/Modules/LocationSharing/MapView/View/UserLocationAnnotationView.swift @@ -48,7 +48,11 @@ class LocationAnnotationView: MGLUserLocationAnnotationView { addUserMarkerView(with: userLocationAnnotation.avatarData) } - + convenience init(userPinLocationAnnotation: MGLAnnotation) { + self.init(annotation: userPinLocationAnnotation, reuseIdentifier: "userPinLocation") + + addPinView() + } convenience init(pinLocationAnnotation: PinLocationAnnotation) { // TODO: Use a reuseIdentifier self.init(annotation: pinLocationAnnotation, reuseIdentifier: nil) @@ -74,6 +78,16 @@ class LocationAnnotationView: MGLUserLocationAnnotationView { addMarkerView(avatarMarkerView) } + private func addPinView() { + guard let pinView = UIHostingController(rootView: Image(uiImage: Asset.Images.locationMarkerIcon.image) + .resizable() + .foregroundColor(theme.colors.accent)).view else { + return + } + + addMarkerView(pinView) + } + private func addPinMarkerView() { guard let pinMarkerView = UIHostingController(rootView: LocationSharingMarkerView(backgroundColor: theme.colors.accent) { Image(uiImage: Asset.Images.locationPinIcon.image) diff --git a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Coordinator/StaticLocationViewingCoordinator.swift b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Coordinator/StaticLocationViewingCoordinator.swift index bf938dac8..09ed605df 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Coordinator/StaticLocationViewingCoordinator.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Coordinator/StaticLocationViewingCoordinator.swift @@ -53,7 +53,8 @@ final class StaticLocationViewingCoordinator: Coordinator, Presentable { mapStyleURL: parameters.session.vc_homeserverConfiguration().tileServer.mapStyleURL, avatarData: parameters.avatarData, location: parameters.location, - coordinateType: parameters.coordinateType + coordinateType: parameters.coordinateType, + service: StaticLocationSharingViewerService() ) let view = StaticLocationView(viewModel: viewModel.context) .environmentObject(AvatarViewModel(avatarService: AvatarService(mediaManager: parameters.mediaManager))) diff --git a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/MockStaticLocationViewingScreenState.swift b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/MockStaticLocationViewingScreenState.swift index 4430c36c2..d2d1ecdf7 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/MockStaticLocationViewingScreenState.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/MockStaticLocationViewingScreenState.swift @@ -46,7 +46,8 @@ enum MockStaticLocationViewingScreenState: MockScreenState, CaseIterable { let viewModel = StaticLocationViewingViewModel(mapStyleURL: mapStyleURL, avatarData: AvatarInput(mxContentUri: "", matrixItemId: "alice:matrix.org", displayName: "Alice"), location: location, - coordinateType: coordinateType) + coordinateType: coordinateType, + service: MockStaticLocationSharingViewerService()) return ([viewModel], AnyView(StaticLocationView(viewModel: viewModel.context) diff --git a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Service/MatrixSDK/StaticLocationSharingViewerService.swift b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Service/MatrixSDK/StaticLocationSharingViewerService.swift new file mode 100644 index 000000000..62be10936 --- /dev/null +++ b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Service/MatrixSDK/StaticLocationSharingViewerService.swift @@ -0,0 +1,32 @@ +// +// Copyright 2023 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import CoreLocation +import Foundation + +class StaticLocationSharingViewerService: StaticLocationSharingViewerServiceProtocol { + + // MARK: Private + + private let locationManager = CLLocationManager() + + // MARK: Public + + func requestAuthorizationIfNeeded() -> Bool { + locationManager.requestAuthorizationIfNeeded() + } +} + diff --git a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Service/Mock/MockStaticLocationSharingViewerService.swift b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Service/Mock/MockStaticLocationSharingViewerService.swift new file mode 100644 index 000000000..e792b8a73 --- /dev/null +++ b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Service/Mock/MockStaticLocationSharingViewerService.swift @@ -0,0 +1,25 @@ +// +// Copyright 2023 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +class MockStaticLocationSharingViewerService: StaticLocationSharingViewerServiceProtocol { + + func requestAuthorizationIfNeeded() -> Bool { + return true + } + +} diff --git a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Service/StaticLocationSharingViewerServiceProtocol.swift b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Service/StaticLocationSharingViewerServiceProtocol.swift new file mode 100644 index 000000000..06a99e1bf --- /dev/null +++ b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Service/StaticLocationSharingViewerServiceProtocol.swift @@ -0,0 +1,22 @@ +// +// Copyright 2023 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +protocol StaticLocationSharingViewerServiceProtocol { + + func requestAuthorizationIfNeeded() -> Bool +} diff --git a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingModels.swift b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingModels.swift index f25b16e48..0789b3ede 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingModels.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingModels.swift @@ -23,6 +23,7 @@ import Foundation enum StaticLocationViewingViewAction { case close case share + case showUserLocation } enum StaticLocationViewingViewModelResult { @@ -42,6 +43,8 @@ struct StaticLocationViewingViewState: BindableState { /// Shared annotation to display existing location let sharedAnnotation: LocationAnnotation + var showsUserLocation = false + var showLoadingIndicator = false var shareButtonEnabled: Bool { diff --git a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingViewModel.swift b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingViewModel.swift index 83bdb0110..d597771bc 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingViewModel.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingViewModel.swift @@ -24,6 +24,7 @@ class StaticLocationViewingViewModel: StaticLocationViewingViewModelType, Static // MARK: Private + private var staticLocationSharingViewerService: StaticLocationSharingViewerServiceProtocol private var mapViewErrorAlertInfoBuilder: MapViewErrorAlertInfoBuilder // MARK: Public @@ -32,7 +33,10 @@ class StaticLocationViewingViewModel: StaticLocationViewingViewModelType, Static // MARK: - Setup - init(mapStyleURL: URL, avatarData: AvatarInputProtocol, location: CLLocationCoordinate2D, coordinateType: LocationSharingCoordinateType) { + init(mapStyleURL: URL, avatarData: AvatarInputProtocol, location: CLLocationCoordinate2D, coordinateType: LocationSharingCoordinateType, service: StaticLocationSharingViewerServiceProtocol) { + + staticLocationSharingViewerService = service + let sharedAnnotation: LocationAnnotation switch coordinateType { case .user: @@ -63,6 +67,8 @@ class StaticLocationViewingViewModel: StaticLocationViewingViewModelType, Static completion?(.close) case .share: completion?(.share(state.sharedAnnotation.coordinate)) + case .showUserLocation: + showsCurrentUserLocation() } } @@ -89,4 +95,12 @@ class StaticLocationViewingViewModel: StaticLocationViewingViewModelType, Static state.bindings.alertInfo = alertInfo } + + private func showsCurrentUserLocation() { + if staticLocationSharingViewerService.requestAuthorizationIfNeeded() { + state.showsUserLocation = true + } else { + state.errorSubject.send(.invalidLocationAuthorization) + } + } } diff --git a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Test/Unit/StaticLocationViewingViewModelTests.swift b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Test/Unit/StaticLocationViewingViewModelTests.swift index f9aa24324..8350975ef 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Test/Unit/StaticLocationViewingViewModelTests.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Test/Unit/StaticLocationViewingViewModelTests.swift @@ -79,10 +79,18 @@ class StaticLocationViewingViewModelTests: XCTestCase { waitForExpectations(timeout: 3) } + func testToggleShowUserLocation() { + let viewModel = buildViewModel() + XCTAssertFalse(viewModel.context.viewState.showsUserLocation) + viewModel.context.send(viewAction: .showUserLocation) + XCTAssertTrue(viewModel.context.viewState.showsUserLocation) + } + private func buildViewModel() -> StaticLocationViewingViewModel { StaticLocationViewingViewModel(mapStyleURL: URL(string: "http://empty.com")!, avatarData: AvatarInput(mxContentUri: "", matrixItemId: "", displayName: ""), location: CLLocationCoordinate2D(latitude: 51.4932641, longitude: -0.257096), - coordinateType: .user) + coordinateType: .user, + service: MockStaticLocationSharingViewerService()) } } diff --git a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/View/StaticLocationView.swift b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/View/StaticLocationView.swift index 3b12fa00c..30144268e 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/View/StaticLocationView.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/View/StaticLocationView.swift @@ -29,19 +29,26 @@ struct StaticLocationView: View { // MARK: Views + var mapView: LocationSharingMapView { + LocationSharingMapView(tileServerMapURL: viewModel.viewState.mapStyleURL, + annotations: [viewModel.viewState.sharedAnnotation], + highlightedAnnotation: viewModel.viewState.sharedAnnotation, + userAvatarData: nil, + showsUserLocation: viewModel.viewState.showsUserLocation, + userLocation: Binding.constant(nil), + mapCenterCoordinate: Binding.constant(nil), + errorSubject: viewModel.viewState.errorSubject) + } + var body: some View { NavigationView { ZStack(alignment: .bottom) { - LocationSharingMapView(tileServerMapURL: viewModel.viewState.mapStyleURL, - annotations: [viewModel.viewState.sharedAnnotation], - highlightedAnnotation: viewModel.viewState.sharedAnnotation, - userAvatarData: viewModel.viewState.userAvatarData, - showsUserLocation: false, - userLocation: Binding.constant(nil), - mapCenterCoordinate: Binding.constant(nil), - errorSubject: viewModel.viewState.errorSubject) + mapView MapCreditsView() } + .overlay(CenterToUserLocationButton(action: { + viewModel.send(viewAction: .showUserLocation) + }).offset(x: -11.0, y: 52), alignment: .topTrailing) .ignoresSafeArea(.all, edges: [.bottom]) .toolbar { ToolbarItem(placement: .navigationBarLeading) { diff --git a/changelog.d/pr-7361.change b/changelog.d/pr-7361.change new file mode 100644 index 000000000..d5927663d --- /dev/null +++ b/changelog.d/pr-7361.change @@ -0,0 +1 @@ +Map Views: Show own location in map views \ No newline at end of file