Render LiveLocations in the map (#5394)

This commit is contained in:
Mauro
2026-04-14 18:27:34 +02:00
committed by GitHub
parent a40fab5f5e
commit 602d779e3a
19 changed files with 380 additions and 163 deletions
@@ -0,0 +1,52 @@
//
// Copyright 2026 Element Creations Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//
import CoreLocation
import SwiftUI
struct LocationShareSheet: View {
let annotation: LocationAnnotation
private var location: CLLocationCoordinate2D {
annotation.coordinate
}
private var senderName: String? {
annotation.kind.displayName ?? annotation.kind.userProfile?.userID
}
var body: some View {
AppActivityView(activityItems: [ShareToMapsAppActivity.MapsAppType.apple.activityURL(for: location, senderName: senderName)],
applicationActivities: ShareToMapsAppActivity.MapsAppType.allCases.map { ShareToMapsAppActivity(type: $0, location: location, senderName: senderName) })
.ignoresSafeArea(edges: .bottom)
.presentationDetents([.medium, .large])
.presentationCompactAdaptation(compactPresentation)
.presentationDragIndicator(.hidden)
}
private var compactPresentation: PresentationAdaptation {
if #available(iOS 26.0, *) {
.none // ShareLinks use a popover presentation on iOS 26, let it match that.
} else {
.sheet
}
}
}
// MARK: - Previews
struct LocationShareSheet_Previews: PreviewProvider {
static let profile = UserProfileProxy(userID: "@alice:example.com", displayName: "Alice")
static let annotation = LocationAnnotation(id: profile.userID,
coordinate: .init(latitude: 51.509865, longitude: -0.118092),
kind: .liveUser(profile))
static var previews: some View {
LocationShareSheet(annotation: annotation)
.previewDisplayName("Live location share sheet")
}
}
@@ -25,8 +25,12 @@ struct LocationSharingScreen: View {
.sheet(isPresented: .constant(true)) {
StaticLocationSheet(context: context)
.alert(item: $context.alertInfo)
.popover(isPresented: $context.showShareSheet) { shareSheet }
.popover(item: $context.sharedAnnotation) { annotation in
LocationShareSheet(annotation: annotation)
}
}
case .viewLive:
mainContent
}
}
@@ -35,7 +39,7 @@ struct LocationSharingScreen: View {
private var mainContent: some View {
mapView
.ignoresSafeArea(edges: .bottom)
.track(screen: context.viewState.isLocationPickerMode ? .LocationSend : .LocationView)
.track(screen: context.viewState.interactionMode == .picker ? .LocationSend : .LocationView)
.navigationTitle(L10n.screenViewLocationTitle)
.navigationBarTitleDisplayMode(.inline)
.toolbar { toolbar }
@@ -45,6 +49,7 @@ struct LocationSharingScreen: View {
ZStack(alignment: .center) {
MapLibreMapView(mapURLBuilder: context.viewState.mapURLBuilder,
options: mapOptions,
mediaProvider: context.mediaProvider,
showsUserLocationMode: $context.showsUserLocationMode,
error: $context.mapError,
mapCenterCoordinate: $context.mapCenterLocation,
@@ -53,10 +58,10 @@ struct LocationSharingScreen: View {
geolocationUncertainty: $context.geolocationUncertainty) {
context.send(viewAction: .userDidPan)
}
.ignoresSafeArea(.all, edges: mapSafeAreaEdges)
.ignoresSafeArea(edges: mapSafeAreaEdges)
if context.viewState.isLocationPickerMode {
LocationMarkerView(kind: context.viewState.locationMarkerKind, mediaProvider: context.mediaProvider)
if let pickerMarkerKind = context.viewState.pickerMarkerKind {
LocationMarkerView(kind: pickerMarkerKind, mediaProvider: context.mediaProvider)
}
}
.overlay(alignment: .topTrailing) {
@@ -64,6 +69,10 @@ struct LocationSharingScreen: View {
}
}
private var mapSafeAreaEdges: Edge.Set {
context.viewState.interactionMode == .picker ? .horizontal : [.horizontal, .bottom]
}
@ToolbarContentBuilder
private var toolbar: some ToolbarContent {
ToolbarItem(placement: .primaryAction) {
@@ -74,25 +83,10 @@ struct LocationSharingScreen: View {
}
private var mapOptions: MapLibreMapView.Options {
var annotations: [String: LocationAnnotation] = [:]
if !context.viewState.isLocationPickerMode {
let kind = context.viewState.locationMarkerKind
let annotation = LocationAnnotation(id: kind.id,
coordinate: context.viewState.initialMapCenter,
anchorPoint: .bottomCenter) {
LocationMarkerView(kind: kind, mediaProvider: context.mediaProvider)
}
annotations[kind.id] = annotation
}
return .init(zoomLevel: context.viewState.zoomLevel,
initialZoomLevel: context.viewState.initialZoomLevel,
mapCenter: context.viewState.initialMapCenter,
annotations: annotations)
}
private var mapSafeAreaEdges: Edge.Set {
context.viewState.isLocationPickerMode ? .horizontal : [.horizontal, .bottom]
.init(zoomLevel: context.viewState.zoomLevel,
initialZoomLevel: context.viewState.initialZoomLevel,
mapCenter: context.viewState.initialMapCenter,
annotations: context.viewState.annotations)
}
@ViewBuilder
@@ -125,26 +119,6 @@ struct LocationSharingScreen: View {
.dynamicTypeSize(.large)
.padding(13)
}
@ViewBuilder
private var shareSheet: some View {
let location = context.viewState.initialMapCenter
let senderName = context.viewState.locationMarkerKind.displayName ?? context.viewState.locationMarkerKind.userProfile?.userID
AppActivityView(activityItems: [ShareToMapsAppActivity.MapsAppType.apple.activityURL(for: location, senderName: senderName)],
applicationActivities: ShareToMapsAppActivity.MapsAppType.allCases.map { ShareToMapsAppActivity(type: $0, location: location, senderName: senderName) })
.ignoresSafeArea(edges: .bottom)
.presentationDetents([.medium, .large])
.presentationCompactAdaptation(shareSheetCompactPresentation)
.presentationDragIndicator(.hidden)
}
private var shareSheetCompactPresentation: PresentationAdaptation {
if #available(iOS 26.0, *) {
.none // ShareLinks use a popover presentation on iOS 26, let it match that.
} else {
.sheet
}
}
}
// MARK: - Previews
@@ -180,7 +154,3 @@ struct LocationSharingScreen_Previews: PreviewProvider, TestablePreview {
.previewDisplayName("Pin Static Location")
}
}
private extension CGPoint {
static let bottomCenter: Self = .init(x: 0.5, y: 1)
}
@@ -34,12 +34,13 @@ struct StaticLocationSheet: View {
.font(.compound.bodyLGSemibold)
.padding(.bottom, 25)
.padding(.top, 29)
if case let .viewStatic(location) = context.viewState.interactionMode {
if case let .viewStatic(location) = context.viewState.interactionMode,
let profile = context.viewState.userProfiles.values.first {
Button {
context.showShareSheet = true
context.sharedAnnotation = context.viewState.annotations.first
} label: {
UserLocationCell(profile: context.viewState.userProfile,
isOwnUser: context.viewState.isOwnUser,
UserLocationCell(profile: profile,
isOwnUser: context.viewState.isOwnUser(profile.userID),
isUserLocation: location.kind == .sender,
timestamp: location.timestamp,
mediaProvider: context.mediaProvider)