Refactor and redesign Location Sharing Screen (#5155)

* refactored the static location screen to the location sharing screen

# Conflicts:
#	UnitTests/Sources/LocationSharingScreenViewModelTests.swift

# Conflicts:
#	ElementX.xcodeproj/project.pbxproj

* implemented a custom pin with an overlayable view

* implemented the render of the user when the location is sender instead of the pin type

* removed description and body they are not used at all.

* reimplemented single button for sharing this or user location + implemented an experimental way to update annotations

* removed unnecessary @Suite description

* implemented a way to display the alert on top of the sheet and added a spinner to the center user location button

* fixed alerts strings

* fixed a failing test

* improved preview tests
This commit is contained in:
Mauro
2026-03-06 12:51:16 +01:00
committed by GitHub
parent 0c79b5404d
commit bbbb2e9dee
85 changed files with 917 additions and 626 deletions
@@ -10,28 +10,84 @@ import Compound
import SwiftUI
struct LocationMarkerView: View {
private let pinColor: Color = .compound.iconOnSolidPrimary
private let pinInsets = EdgeInsets(top: 13, leading: 12, bottom: 15, trailing: 12)
var userProfile: UserProfileProxy?
var fillColor = Color.compound.bgCanvasDefault
var strokeColor = Color.compound.iconSecondaryAlpha
var dotColor = Color.compound.iconPrimary
@ScaledMetric var size: CGFloat = 42
var mediaProvider: MediaProviderProtocol?
private let circleCenter = CGPoint(x: 21, y: 21) // in SVG space
private let circleRadius: CGFloat = 6 // in SVG space
var body: some View {
CompoundIcon(\.locationPinSolid)
.dynamicTypeSize(.large)
.foregroundStyle(pinColor)
.padding(pinInsets)
.background {
backgroundShape
.shadow(color: .black.opacity(0.2), radius: 4.1129, x: 0, y: 4.93548)
// Generated from the SVG
Canvas { context, canvasSize in
let scaleX = canvasSize.width / 42
let scaleY = canvasSize.height / 50
let pinPath = Path { p in
p.move(to: CGPoint(x: 21 * scaleX, y: 49 * scaleY))
p.addCurve(to: CGPoint(x: 17.6875 * scaleX, y: 47.7581 * scaleY),
control1: CGPoint(x: 19.25 * scaleX, y: 49 * scaleY),
control2: CGPoint(x: 18.1458 * scaleX, y: 48.4825 * scaleY))
p.addCurve(to: CGPoint(x: 5.28125 * scaleX, y: 33.6313 * scaleY),
control1: CGPoint(x: 12.5833 * scaleX, y: 42.8525 * scaleY),
control2: CGPoint(x: 8.41667 * scaleX, y: 38.1332 * scaleY))
p.addCurve(to: CGPoint(x: 1 * scaleX, y: 21.3674 * scaleY),
control1: CGPoint(x: 2.82292 * scaleX, y: 29.2846 * scaleY),
control2: CGPoint(x: 1 * scaleX, y: 25.1863 * scaleY))
p.addCurve(to: CGPoint(x: 21 * scaleX, y: 1 * scaleY),
control1: CGPoint(x: 1 * scaleX, y: 10.2109 * scaleY),
control2: CGPoint(x: 9.5 * scaleX, y: 1 * scaleY))
p.addCurve(to: CGPoint(x: 41 * scaleX, y: 21.3674 * scaleY),
control1: CGPoint(x: 32.5 * scaleX, y: 1 * scaleY),
control2: CGPoint(x: 41 * scaleX, y: 10.2109 * scaleY))
p.addCurve(to: CGPoint(x: 36.7188 * scaleX, y: 33.6313 * scaleY),
control1: CGPoint(x: 41 * scaleX, y: 25.1863 * scaleY),
control2: CGPoint(x: 39.1771 * scaleX, y: 29.2846 * scaleY))
p.addCurve(to: CGPoint(x: 24.3125 * scaleX, y: 47.7581 * scaleY),
control1: CGPoint(x: 33.5833 * scaleX, y: 38.1332 * scaleY),
control2: CGPoint(x: 29.4167 * scaleX, y: 42.8525 * scaleY))
p.addCurve(to: CGPoint(x: 21 * scaleX, y: 49 * scaleY),
control1: CGPoint(x: 23.3333 * scaleX, y: 48.4825 * scaleY),
control2: CGPoint(x: 21.5833 * scaleX, y: 49 * scaleY))
p.closeSubpath()
}
.alignmentGuide(VerticalAlignment.center) { dimensions in
dimensions[.bottom]
context.stroke(pinPath, with: .color(strokeColor), lineWidth: 2 * scaleX)
context.fill(pinPath, with: .color(fillColor))
// Dot
let dotPath = Path(ellipseIn: CGRect(x: (circleCenter.x - circleRadius) * scaleX,
y: (circleCenter.y - circleRadius) * scaleY,
width: circleRadius * 2 * scaleX,
height: circleRadius * 2 * scaleY))
context.fill(dotPath, with: .color(dotColor))
// Draw resolved symbol centered on the circle
if userProfile != nil, let symbol = context.resolveSymbol(id: 0) {
let center = CGPoint(x: circleCenter.x * scaleX,
y: circleCenter.y * scaleY)
context.draw(symbol, at: center, anchor: .center)
}
}
var backgroundShape: some View {
Image(asset: Asset.Images.locationMarkerShape)
.resizable()
.aspectRatio(contentMode: .fill)
.foregroundStyle(.compound.iconPrimary)
} symbols: {
if let userProfile {
LoadableAvatarImage(url: userProfile.avatarURL,
name: userProfile.displayName,
contentID: userProfile.userID,
avatarSize: .user(on: .map),
mediaProvider: mediaProvider)
.overlay {
Circle().inset(by: 0.5).stroke(strokeColor)
}
.tag(0)
}
}
.frame(width: size, height: size * 50 / 42)
.alignmentGuide(VerticalAlignment.center) { dimensions in
dimensions[.bottom]
}
}
}
@@ -39,9 +95,19 @@ struct LocationMarkerView_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
VStack(spacing: 30) {
LocationMarkerView()
LocationMarkerView()
.colorScheme(.dark)
LocationMarkerView(userProfile: UserProfileProxy.mockDan,
mediaProvider: MediaProviderMock(configuration: .init()))
LocationMarkerView(userProfile: UserProfileProxy.mockDan,
mediaProvider: MediaProviderMock(configuration: .init()))
.colorScheme(.dark)
}
.padding(16)
.background(Color(red: 0.9, green: 0.85, blue: 0.8))
.previewLayout(.sizeThatFits)
}
}