diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_center_map_icon.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Location/location_center_map_icon.imageset/Contents.json new file mode 100644 index 000000000..7934b19cd --- /dev/null +++ b/Riot/Assets/Images.xcassets/Room/Location/location_center_map_icon.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "location_center_map_icon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "location_center_map_icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "location_center_map_icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_center_map_icon.imageset/location_center_map_icon.png b/Riot/Assets/Images.xcassets/Room/Location/location_center_map_icon.imageset/location_center_map_icon.png new file mode 100644 index 000000000..6b73b4557 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_center_map_icon.imageset/location_center_map_icon.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_center_map_icon.imageset/location_center_map_icon@2x.png b/Riot/Assets/Images.xcassets/Room/Location/location_center_map_icon.imageset/location_center_map_icon@2x.png new file mode 100644 index 000000000..d9f6f7297 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_center_map_icon.imageset/location_center_map_icon@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/Location/location_center_map_icon.imageset/location_center_map_icon@3x.png b/Riot/Assets/Images.xcassets/Room/Location/location_center_map_icon.imageset/location_center_map_icon@3x.png new file mode 100644 index 000000000..f68f36c74 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Location/location_center_map_icon.imageset/location_center_map_icon@3x.png differ diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift index 0863cd042..ded98f204 100644 --- a/Riot/Generated/Images.swift +++ b/Riot/Generated/Images.swift @@ -173,6 +173,7 @@ internal class Asset: NSObject { internal static let voiceCallHangonIcon = ImageAsset(name: "voice_call_hangon_icon") internal static let voiceCallHangupIcon = ImageAsset(name: "voice_call_hangup_icon") internal static let liveLocationIcon = ImageAsset(name: "live_location_icon") + internal static let locationCenterMapIcon = ImageAsset(name: "location_center_map_icon") internal static let locationLiveIcon = ImageAsset(name: "location_live_icon") internal static let locationMarkerIcon = ImageAsset(name: "location_marker_icon") internal static let locationPinIcon = ImageAsset(name: "location_pin_icon") diff --git a/Riot/Utils/CLLocationCoordinate2D.swift b/Riot/Utils/CLLocationCoordinate2D.swift new file mode 100644 index 000000000..cdc21e100 --- /dev/null +++ b/Riot/Utils/CLLocationCoordinate2D.swift @@ -0,0 +1,33 @@ +// +// Copyright 2022 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 +import CoreLocation + +extension CLLocationCoordinate2D { + + /// Compare two coordinates + /// - parameter coordinate: another coordinate to compare + /// - parameter precision:it represente how close you want the two coordinates + /// - return: bool value + func isEqual(to coordinate: CLLocationCoordinate2D, precision: Double) -> Bool { + + if fabs(self.latitude - coordinate.latitude) <= precision && fabs(self.longitude - coordinate.longitude) <= precision { + return true + } + return false + } +} diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingModels.swift b/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingModels.swift index f06d80562..834768135 100644 --- a/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingModels.swift +++ b/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingModels.swift @@ -22,6 +22,8 @@ import CoreLocation enum LocationSharingViewAction { case cancel case share + case sharePinLocation + case goToUserLocation } enum LocationSharingViewModelResult { @@ -55,7 +57,9 @@ struct LocationSharingViewState: BindableState { var highlightedAnnotation: UserLocationAnnotation? /// Indicates whether the user has moved around the map to drop a pin somewhere other than their current location - var isPinDropSharing: Bool = false + var isPinDropSharing: Bool { + return bindings.pinLocation != nil + } var showLoadingIndicator: Bool = false @@ -85,6 +89,7 @@ struct LocationSharingViewState: BindableState { struct LocationSharingViewStateBindings { var alertInfo: AlertInfo? var userLocation: CLLocationCoordinate2D? + var pinLocation: CLLocationCoordinate2D? } enum LocationSharingAlertType { diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingViewModel.swift b/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingViewModel.swift index bccd95391..a0fcc42b0 100644 --- a/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingViewModel.swift +++ b/RiotSwiftUI/Modules/Room/LocationSharing/LocationSharingViewModel.swift @@ -91,6 +91,15 @@ class LocationSharingViewModel: LocationSharingViewModelType, LocationSharingVie } completion?(.share(latitude: location.latitude, longitude: location.longitude)) + case .sharePinLocation: + guard let pinLocation = state.bindings.pinLocation else { + processError(.failedLocatingUser) + return + } + + completion?(.share(latitude: pinLocation.latitude, longitude: pinLocation.longitude)) + case .goToUserLocation: + state.bindings.pinLocation = nil } } diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingMapView.swift b/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingMapView.swift index 0ca25dc6b..d0340e47f 100644 --- a/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingMapView.swift +++ b/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingMapView.swift @@ -47,6 +47,9 @@ struct LocationSharingMapView: UIViewRepresentable { /// Last user location if `showsUserLocation` has been enabled @Binding var userLocation: CLLocationCoordinate2D? + /// Coordinate of the center of the map + @Binding var mapCenterCoordinate: CLLocationCoordinate2D? + /// Publish view errors if any let errorSubject: PassthroughSubject @@ -68,7 +71,7 @@ struct LocationSharingMapView: UIViewRepresentable { mapView.setCenter(highlightedAnnotation.coordinate, zoomLevel: Constants.mapZoomLevel, animated: false) } - if self.showsUserLocation { + if self.showsUserLocation && mapCenterCoordinate == nil { mapView.showsUserLocation = true mapView.userTrackingMode = .follow } else { @@ -145,6 +148,16 @@ extension LocationSharingMapView { break } } + + func mapView(_ mapView: MGLMapView, regionDidChangeAnimated animated: Bool) { + let mapCenterCoordinate = mapView.centerCoordinate + // Prevent this function to set pinLocation when the map is openning + guard let userLocation = locationSharingMapView.userLocation, + !userLocation.isEqual(to: mapCenterCoordinate, precision: 0.0000000001) else { + return + } + locationSharingMapView.mapCenterCoordinate = mapCenterCoordinate + } } } diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingMarkerView.swift b/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingMarkerView.swift index 3c36e7d50..3910b053f 100644 --- a/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingMarkerView.swift +++ b/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingMarkerView.swift @@ -40,6 +40,7 @@ struct LocationSharingMarkerView: View { markerImage .frame(width: 40, height: 40) } + .offset(x: 0, y: -23) } } diff --git a/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingView.swift b/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingView.swift index ce3ae579c..430ffdfe5 100644 --- a/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingView.swift +++ b/RiotSwiftUI/Modules/Room/LocationSharing/View/LocationSharingView.swift @@ -33,13 +33,7 @@ struct LocationSharingView: View { var body: some View { NavigationView { ZStack(alignment: .bottom) { - LocationSharingMapView(tileServerMapURL: context.viewState.mapStyleURL, - annotations: context.viewState.annotations, - highlightedAnnotation: context.viewState.highlightedAnnotation, - userAvatarData: context.viewState.userAvatarData, - showsUserLocation: context.viewState.showsUserLocation, - userLocation: $context.userLocation, - errorSubject: context.viewState.errorSubject) + mapView VStack(spacing: 0) { MapCreditsView() if context.viewState.shareButtonVisible { @@ -85,6 +79,39 @@ struct LocationSharingView: View { .navigationViewStyle(StackNavigationViewStyle()) } + var mapView: some View { + ZStack(alignment: .topTrailing) { + ZStack(alignment: .center) { + LocationSharingMapView(tileServerMapURL: context.viewState.mapStyleURL, + annotations: context.viewState.annotations, + highlightedAnnotation: context.viewState.highlightedAnnotation, + userAvatarData: context.viewState.userAvatarData, + showsUserLocation: context.viewState.showsUserLocation, + userLocation: $context.userLocation, + mapCenterCoordinate: $context.pinLocation, + errorSubject: context.viewState.errorSubject) + if context.viewState.isPinDropSharing { + LocationSharingMarkerView(backgroundColor: theme.colors.accent) { + Image(uiImage: Asset.Images.locationPinIcon.image) + .resizable() + .shapedBorder(color: theme.colors.accent, borderWidth: 3, shape: Circle()) + } + } + } + Button { + context.send(viewAction: .goToUserLocation) + } label: { + Image(uiImage: Asset.Images.locationCenterMapIcon.image) + .foregroundColor(theme.colors.accent) + } + .padding(6.0) + .background(theme.colors.background) + .clipShape(RoundedCornerShape(radius: 4, corners: [.allCorners])) + .shadow(radius: 2.0) + .offset(x: -11.0, y: 52) + } + } + var buttonsView: some View { VStack(alignment: .leading, spacing: 15) { if !context.viewState.isPinDropSharing { @@ -107,7 +134,7 @@ struct LocationSharingView: View { } } else { LocationSharingOptionButton(text: VectorL10n.locationSharingPinDropShareTitle) { - // TODO: - Pin drop sharing action + context.send(viewAction: .sharePinLocation) } buttonIcon: { Image(uiImage: Asset.Images.locationPinIcon.image) .resizable() diff --git a/changelog.d/5858.change b/changelog.d/5858.change new file mode 100644 index 000000000..3d45b56d9 --- /dev/null +++ b/changelog.d/5858.change @@ -0,0 +1 @@ +Location sharing: Add the ability for the user to share static location of a pin anywhere on the map