Show own location in map views (#7375)

* show own location on static sharing

* show own location on live sharing

* add tests and changelog

* check location authorisation, fix center to current user location button

* it moves request to LocationManager in proper service

* add static location viewer service
This commit is contained in:
Flescio
2023-02-20 18:07:03 +01:00
committed by GitHub
parent d15131e9ba
commit 95501bfdde
21 changed files with 267 additions and 36 deletions
@@ -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)))
@@ -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)
@@ -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()
}
}
@@ -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
}
}
@@ -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
}
@@ -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 {
@@ -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)
}
}
}
@@ -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())
}
}
@@ -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) {