Send pin-drop location (#1179)

* add location sharing action in room, and open location sharing screen

* add pin location sharing

* fix asset, add tests for location viewModel, add send location request

* fix map zoom level, fix assets for location

* add feature flag for location sharing

* hide attribution button
This commit is contained in:
Flescio
2023-06-28 11:39:38 +02:00
committed by GitHub
parent e25ca2e120
commit 102e6a64fa
29 changed files with 548 additions and 42 deletions
@@ -14,6 +14,7 @@
// limitations under the License.
//
import CoreLocation
import Foundation
enum LocationSharingViewError: Error, Hashable {
@@ -21,13 +22,23 @@ enum LocationSharingViewError: Error, Hashable {
case mapError(MapLibreError)
}
enum StaticLocationScreenViewModelAction { }
enum StaticLocationScreenViewModelAction {
case close
case sendLocation(GeoURI)
}
struct StaticLocationScreenViewState: BindableState {
/// Indicates whether the user has moved around the map to drop a pin somewhere other than their current location
var isPinDropSharing = true
/// Behavior mode of the current user's location, can be hidden, only shown and shown following the user
var showsUserLocationMode: ShowUserLocationMode = .hide
var bindings = StaticLocationScreenBindings()
}
struct StaticLocationScreenBindings {
var mapCenterLocation: CLLocationCoordinate2D?
/// Information describing the currently displayed alert.
var mapError: MapLibreError? {
get {
@@ -45,4 +56,8 @@ struct StaticLocationScreenBindings {
var alertInfo: AlertInfo<LocationSharingViewError>?
}
enum StaticLocationScreenViewAction { }
enum StaticLocationScreenViewAction {
case close
case selectLocation
case userDidPan
}
@@ -19,12 +19,22 @@ import SwiftUI
struct StaticLocationScreenCoordinatorParameters { }
enum StaticLocationScreenCoordinatorAction { }
enum StaticLocationScreenCoordinatorAction {
case close
case selectedLocation(GeoURI)
}
final class StaticLocationScreenCoordinator: CoordinatorProtocol {
let parameters: StaticLocationScreenCoordinatorParameters
let viewModel: StaticLocationScreenViewModelProtocol
private let actionsSubject: PassthroughSubject<StaticLocationScreenCoordinatorAction, Never> = .init()
private var cancellables: Set<AnyCancellable> = .init()
var actions: AnyPublisher<StaticLocationScreenCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(parameters: StaticLocationScreenCoordinatorParameters) {
self.parameters = parameters
@@ -33,6 +43,19 @@ final class StaticLocationScreenCoordinator: CoordinatorProtocol {
// MARK: - Public
func start() {
viewModel.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .close:
actionsSubject.send(.close)
case .sendLocation(let geoURI):
actionsSubject.send(.selectedLocation(geoURI))
}
}
.store(in: &cancellables)
}
func toPresentable() -> AnyView {
AnyView(StaticLocationScreen(context: viewModel.context))
}
@@ -17,7 +17,7 @@
import Combine
import Foundation
typealias StaticLocationScreenViewModelType = StateStoreViewModel<StaticLocationScreenViewState, StaticLocationScreenViewModelAction>
typealias StaticLocationScreenViewModelType = StateStoreViewModel<StaticLocationScreenViewState, StaticLocationScreenViewAction>
class StaticLocationScreenViewModel: StaticLocationScreenViewModelType, StaticLocationScreenViewModelProtocol {
private let actionsSubject: PassthroughSubject<StaticLocationScreenViewModelAction, Never> = .init()
@@ -29,4 +29,17 @@ class StaticLocationScreenViewModel: StaticLocationScreenViewModelType, StaticLo
init() {
super.init(initialViewState: StaticLocationScreenViewState())
}
override func process(viewAction: StaticLocationScreenViewAction) {
switch viewAction {
case .close:
actionsSubject.send(.close)
case .selectLocation:
guard let coordinate = state.bindings.mapCenterLocation else { return }
actionsSubject.send(.sendLocation(.init(coordinate: coordinate)))
case .userDidPan:
state.showsUserLocationMode = .hide
state.isPinDropSharing = true
}
}
}
@@ -22,18 +22,62 @@ struct StaticLocationScreen: View {
private let builder = MapTilerStyleBuilder(appSettings: ServiceLocator.shared.settings)
var body: some View {
NavigationView {
mapView
.ignoresSafeArea(.all, edges: [.bottom])
.navigationBarTitleDisplayMode(.inline)
.alert(item: $context.alertInfo)
mapView
.ignoresSafeArea(.all, edges: .horizontal)
.navigationTitle(L10n.screenShareLocationTitle)
.navigationBarTitleDisplayMode(.inline)
.toolbar { toolbar }
.alert(item: $context.alertInfo)
}
private var mapView: some View {
ZStack(alignment: .center) {
MapLibreMapView(builder: builder,
showsUserLocationMode: .hide,
error: $context.mapError,
mapCenterCoordinate: $context.mapCenterLocation,
userDidPan: {
context.send(viewAction: .userDidPan)
})
if context.viewState.isPinDropSharing {
LocationMarkerView()
}
}
}
var mapView: MapLibreMapView {
MapLibreMapView(builder: builder,
showsUserLocationMode: .follow,
error: $context.mapError)
@ToolbarContentBuilder
private var toolbar: some ToolbarContent {
ToolbarItem(placement: .navigationBarLeading) {
closeButton
}
ToolbarItemGroup(placement: .bottomBar) {
shareLocationButton
Spacer()
}
}
@ScaledMetric private var shareMarkerSize: CGFloat = 28
private var shareLocationButton: some View {
Button {
context.send(viewAction: .selectLocation)
} label: {
HStack(spacing: 8) {
Image(asset: Asset.Images.locationMarker)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: shareMarkerSize, height: shareMarkerSize)
Text(context.viewState.isPinDropSharing ? L10n.screenShareThisLocationAction : L10n.screenShareMyLocationAction)
}
}
}
private var closeButton: some View {
Button(L10n.actionCancel, action: close)
}
private func close() {
context.send(viewAction: .close)
}
}