* Add LiveLocationSheet and refactor existing views to share code * Implement logic for highlighting a specific LLS from a user once selected in the sheet * Updated tests, project and added new previews for the LLS sheet. # Conflicts: # PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationRoomTimelineView.Bubbles-iPad-pseudo.png # PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationRoomTimelineView.Bubbles-iPhone-pseudo.png * add Equatable conformance to CLLocationCoordinate2D
97 lines
4.2 KiB
Swift
97 lines
4.2 KiB
Swift
//
|
|
// 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 Compound
|
|
import SwiftUI
|
|
|
|
struct LiveLocationSheet: View {
|
|
@Bindable var context: LocationSharingScreenViewModel.Context
|
|
@State private var currentDetent: PresentationDetent = supportedDetents[1]
|
|
|
|
private static let supportedDetents: [PresentationDetent] = [.fraction(0.13), .fraction(0.3)]
|
|
|
|
private var isCurrentDetentSmall: Bool {
|
|
currentDetent == Self.supportedDetents[0]
|
|
}
|
|
|
|
var body: some View {
|
|
mainContent
|
|
.interactiveDismissDisabled()
|
|
.presentationBackground(.compound.bgCanvasDefault)
|
|
.presentationBackgroundInteraction(.enabled)
|
|
.presentationDragIndicator(context.viewState.liveLocationShares.isEmpty ? .hidden : .visible)
|
|
.presentationDetents(context.viewState.liveLocationShares.isEmpty ? .init([Self.supportedDetents[0]]) : .init(Self.supportedDetents),
|
|
selection: $currentDetent)
|
|
.animation(.elementDefault, value: currentDetent)
|
|
.animation(.elementDefault, value: context.viewState.liveLocationShares.isEmpty)
|
|
}
|
|
|
|
private var mainContent: some View {
|
|
VStack(spacing: 0) {
|
|
title
|
|
if isCurrentDetentSmall {
|
|
subtitle
|
|
} else {
|
|
locationSharesList
|
|
}
|
|
}
|
|
.popover(item: $context.sharedAnnotation) { annotation in
|
|
LocationShareSheet(annotation: annotation)
|
|
}
|
|
}
|
|
|
|
private var title: some View {
|
|
Text(context.viewState.liveLocationShares.isEmpty ? L10n.screenLiveLocationSheetNobodySharing : L10n.screenLiveLocationSheetTitle)
|
|
.foregroundStyle(.compound.textPrimary)
|
|
.font(.compound.bodyLGSemibold)
|
|
.padding(.bottom, isCurrentDetentSmall ? 0 : 25)
|
|
.padding(.top, isCurrentDetentSmall ? 0 : 29)
|
|
}
|
|
|
|
private var subtitle: some View {
|
|
Text(L10n.screenLiveLocationSheetSubtitle(context.viewState.liveLocationShares.count))
|
|
.font(.compound.bodySM)
|
|
.foregroundStyle(.compound.textSecondary)
|
|
.opacity(context.viewState.liveLocationShares.isEmpty ? 0 : 1)
|
|
.allowsHitTesting(!context.viewState.liveLocationShares.isEmpty)
|
|
}
|
|
|
|
private var locationSharesList: some View {
|
|
ScrollView {
|
|
LazyVStack(spacing: 0) {
|
|
ForEach(context.viewState.liveLocationShares) { liveLocationShare in
|
|
if let profile = context.viewState.userProfiles[liveLocationShare.userID] {
|
|
Button {
|
|
guard let geoURI = liveLocationShare.geoURI else { return }
|
|
context.mapCenterLocation = .init(latitude: geoURI.latitude, longitude: geoURI.longitude)
|
|
} label: {
|
|
UserLocationCell(profile: profile,
|
|
isOwnUser: context.viewState.isOwnUser(liveLocationShare.userID),
|
|
kind: .live,
|
|
mediaProvider: context.mediaProvider,
|
|
onShare: { context.sharedAnnotation = context.viewState.annotations.first { $0.id == liveLocationShare.id }},
|
|
onStop: { context.send(viewAction: .stopLiveLocation) })
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct LiveLocationSheet_Previews: PreviewProvider, TestablePreview {
|
|
static let viewModel = LocationSharingScreenViewModel.mock(type: .viewLive, senderID: RoomMemberProxyMock.mockMe.userID)
|
|
static let emptyViewModel = LocationSharingScreenViewModel.mock(type: .viewLiveEmpty, senderID: RoomMemberProxyMock.mockMe.userID)
|
|
|
|
static var previews: some View {
|
|
LiveLocationSheet(context: viewModel.context)
|
|
.previewDisplayName("Live location")
|
|
LiveLocationSheet(context: emptyViewModel.context)
|
|
.previewDisplayName("Live locations are empty")
|
|
}
|
|
}
|