mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 01:20:12 +00:00
implementing static location sheet
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
|
||||
import CoreLocation
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
enum LocationSharingViewError: Error, Hashable {
|
||||
case missingAuthorization
|
||||
@@ -21,25 +22,18 @@ enum LocationSharingScreenViewModelAction {
|
||||
|
||||
enum LocationSharingInteractionMode: Hashable {
|
||||
case picker
|
||||
case viewStatic(senderID: String?, geoURI: GeoURI)
|
||||
|
||||
var canShowAvatar: Bool {
|
||||
switch self {
|
||||
case .picker, .viewStatic(.some(_), _):
|
||||
true
|
||||
default:
|
||||
false
|
||||
}
|
||||
}
|
||||
case viewStatic(StaticLocationData)
|
||||
}
|
||||
|
||||
struct LocationSharingScreenViewState: BindableState {
|
||||
init(interactionMode: LocationSharingInteractionMode,
|
||||
mapURLBuilder: MapTilerURLBuilderProtocol,
|
||||
showLiveLocationSharingButton: Bool) {
|
||||
showLiveLocationSharingButton: Bool,
|
||||
ownUserID: String) {
|
||||
self.interactionMode = interactionMode
|
||||
self.mapURLBuilder = mapURLBuilder
|
||||
self.showLiveLocationSharingButton = showLiveLocationSharingButton
|
||||
self.ownUserID = ownUserID
|
||||
|
||||
bindings.showsUserLocationMode = switch interactionMode {
|
||||
case .picker: .showAndFollow
|
||||
@@ -50,6 +44,7 @@ struct LocationSharingScreenViewState: BindableState {
|
||||
let interactionMode: LocationSharingInteractionMode
|
||||
let mapURLBuilder: MapTilerURLBuilderProtocol
|
||||
let showLiveLocationSharingButton: Bool
|
||||
let ownUserID: String
|
||||
|
||||
var bindings = LocationSharingScreenBindings(showsUserLocationMode: .hide)
|
||||
|
||||
@@ -62,9 +57,9 @@ struct LocationSharingScreenViewState: BindableState {
|
||||
switch interactionMode {
|
||||
case .picker:
|
||||
// middle point in Europe, to be used if the users location is not yet known
|
||||
return .init(latitude: 49.843, longitude: 9.902056)
|
||||
case .viewStatic(_, let geoURI):
|
||||
return .init(latitude: geoURI.latitude, longitude: geoURI.longitude)
|
||||
.init(latitude: 49.843, longitude: 9.902056)
|
||||
case .viewStatic(let location):
|
||||
.init(latitude: location.geoURI.latitude, longitude: location.geoURI.longitude)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,15 +71,6 @@ struct LocationSharingScreenViewState: BindableState {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
var showShareAction: Bool {
|
||||
switch interactionMode {
|
||||
case .picker:
|
||||
return false
|
||||
case .viewStatic:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
var isLocationLoading: Bool {
|
||||
!bindings.hasLoadedUserLocation && bindings.isLocationAuthorized != false
|
||||
@@ -105,12 +91,12 @@ struct LocationSharingScreenViewState: BindableState {
|
||||
|
||||
var userProfile: UserProfileProxy?
|
||||
|
||||
var shownUserProfile: UserProfileProxy? {
|
||||
var staticLocationMarkerUserProfile: UserProfileProxy? {
|
||||
switch interactionMode {
|
||||
case .picker:
|
||||
isSharingUserLocation ? userProfile : nil
|
||||
case .viewStatic:
|
||||
userProfile
|
||||
case .viewStatic(let location):
|
||||
location.kind == .sender ? userProfile : nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,3 +158,10 @@ extension AlertInfo where T == LocationSharingViewError {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct StaticLocationData: Hashable {
|
||||
let sender: TimelineItemSender
|
||||
let geoURI: GeoURI
|
||||
let kind: StaticLocationKind
|
||||
let timestamp: Date
|
||||
}
|
||||
|
||||
@@ -37,13 +37,12 @@ class LocationSharingScreenViewModel: LocationSharingScreenViewModelType, Locati
|
||||
|
||||
super.init(initialViewState: .init(interactionMode: interactionMode,
|
||||
mapURLBuilder: mapURLBuilder,
|
||||
showLiveLocationSharingButton: liveLocationSharingEnabled),
|
||||
showLiveLocationSharingButton: liveLocationSharingEnabled,
|
||||
ownUserID: roomProxy.ownUserID),
|
||||
mediaProvider: mediaProvider)
|
||||
|
||||
if interactionMode.canShowAvatar {
|
||||
updateShownUserProfile(members: roomProxy.membersPublisher.value)
|
||||
setupSubscriptions()
|
||||
}
|
||||
updateShownUserProfile(members: roomProxy.membersPublisher.value)
|
||||
setupSubscriptions()
|
||||
}
|
||||
|
||||
override func process(viewAction: LocationSharingScreenViewAction) {
|
||||
@@ -86,14 +85,12 @@ class LocationSharingScreenViewModel: LocationSharingScreenViewModelType, Locati
|
||||
} else {
|
||||
state.userProfile = .init(userID: roomProxy.ownUserID)
|
||||
}
|
||||
case .viewStatic(.some(let senderID), _):
|
||||
if let sender = members.first(where: { $0.userID == senderID }).map(UserProfileProxy.init) {
|
||||
case .viewStatic(let location):
|
||||
if let sender = members.first(where: { $0.userID == location.sender.id }).map(UserProfileProxy.init) {
|
||||
state.userProfile = sender
|
||||
} else {
|
||||
state.userProfile = .init(userID: senderID)
|
||||
state.userProfile = .init(sender: location.sender)
|
||||
}
|
||||
default:
|
||||
state.userProfile = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,3 +124,39 @@ class LocationSharingScreenViewModel: LocationSharingScreenViewModelType, Locati
|
||||
"\(Self.self)-Status"
|
||||
}
|
||||
}
|
||||
|
||||
extension LocationSharingScreenViewModel {
|
||||
enum MockType {
|
||||
case picker
|
||||
case staticSenderLocation
|
||||
case staticPinLocation
|
||||
}
|
||||
|
||||
static func mock(type: MockType, senderID: String = "@dan:matrix.org") -> LocationSharingScreenViewModel {
|
||||
let interactionMode: LocationSharingInteractionMode = switch type {
|
||||
case .picker:
|
||||
.picker
|
||||
case .staticPinLocation:
|
||||
.viewStatic(.init(sender: .init(id: senderID),
|
||||
geoURI: .init(latitude: 41.9027835,
|
||||
longitude: 12.4963655),
|
||||
kind: .pin,
|
||||
timestamp: .mock))
|
||||
case .staticSenderLocation:
|
||||
.viewStatic(.init(sender: .init(id: senderID),
|
||||
geoURI: .init(latitude: 41.9027835,
|
||||
longitude: 12.4963655),
|
||||
kind: .sender,
|
||||
timestamp: .mock))
|
||||
}
|
||||
|
||||
return LocationSharingScreenViewModel(interactionMode: interactionMode,
|
||||
mapURLBuilder: ServiceLocator.shared.settings.mapTilerConfiguration,
|
||||
liveLocationSharingEnabled: true,
|
||||
roomProxy: JoinedRoomProxyMock(.init()),
|
||||
timelineController: MockTimelineController(),
|
||||
analytics: ServiceLocator.shared.analytics,
|
||||
userIndicatorController: UserIndicatorControllerMock(),
|
||||
mediaProvider: MediaProviderMock(configuration: .init()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
//
|
||||
// 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 LocationPickerSheet: View {
|
||||
@Bindable var context: LocationSharingScreenViewModel.Context
|
||||
@State private var height: CGFloat = .zero
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Button {
|
||||
context.send(viewAction: .selectLocation)
|
||||
} label: {
|
||||
if context.viewState.isSharingUserLocation {
|
||||
LocationPickerLabel(text: L10n.screenShareMyLocationAction,
|
||||
icon: \.locationNavigatorCentred,
|
||||
iconColor: .compound.iconSecondary)
|
||||
} else {
|
||||
LocationPickerLabel(text: L10n.screenShareThisLocationAction,
|
||||
icon: \.locationNavigator,
|
||||
iconColor: .compound.iconSecondary)
|
||||
}
|
||||
}
|
||||
if context.viewState.showLiveLocationSharingButton {
|
||||
Button { } label: {
|
||||
LocationPickerLabel(text: L10n.actionShareLiveLocation,
|
||||
icon: \.locationPinSolid,
|
||||
iconColor: .compound.iconAccentPrimary)
|
||||
}
|
||||
}
|
||||
}
|
||||
.font(.compound.bodyLG)
|
||||
.foregroundStyle(.compound.textPrimary)
|
||||
.padding(.top, 38)
|
||||
.readHeight($height)
|
||||
.interactiveDismissDisabled()
|
||||
.presentationBackground(.compound.bgCanvasDefault)
|
||||
.presentationBackgroundInteraction(.enabled)
|
||||
.presentationDragIndicator(.hidden)
|
||||
.presentationDetents([.height(height)])
|
||||
}
|
||||
}
|
||||
|
||||
private struct LocationPickerLabel: View {
|
||||
let text: String
|
||||
let icon: KeyPath<CompoundIcons, Image>
|
||||
let iconColor: Color
|
||||
|
||||
var body: some View {
|
||||
Label {
|
||||
Text(text)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.vertical, 14)
|
||||
.rowDivider(alignment: .top)
|
||||
.padding(.trailing, 16)
|
||||
} icon: {
|
||||
CompoundIcon(icon)
|
||||
.foregroundStyle(iconColor)
|
||||
}
|
||||
.padding(.leading, 16)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
|
||||
struct LocationPickerSheet_Previews: PreviewProvider, TestablePreview {
|
||||
static let viewModel = LocationSharingScreenViewModel.mock(type: .picker)
|
||||
|
||||
static var previews: some View {
|
||||
LocationPickerSheet(context: viewModel.context)
|
||||
}
|
||||
}
|
||||
@@ -13,15 +13,19 @@ struct LocationSharingScreen: View {
|
||||
@Bindable var context: LocationSharingScreenViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
if context.viewState.isLocationPickerMode {
|
||||
switch context.viewState.interactionMode {
|
||||
case .picker:
|
||||
mainContent
|
||||
.sheet(isPresented: .constant(true)) {
|
||||
sharingOptionsSheet
|
||||
LocationPickerSheet(context: context)
|
||||
.alert(item: $context.alertInfo)
|
||||
}
|
||||
} else {
|
||||
case .viewStatic:
|
||||
mainContent
|
||||
.alert(item: $context.alertInfo)
|
||||
.sheet(isPresented: .constant(true)) {
|
||||
StaticLocationSheet(context: context)
|
||||
.alert(item: $context.alertInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +55,7 @@ struct LocationSharingScreen: View {
|
||||
.ignoresSafeArea(.all, edges: mapSafeAreaEdges)
|
||||
|
||||
if context.viewState.isLocationPickerMode {
|
||||
LocationMarkerView(userProfile: context.viewState.shownUserProfile, mediaProvider: context.mediaProvider)
|
||||
LocationMarkerView(userProfile: context.viewState.staticLocationMarkerUserProfile, mediaProvider: context.mediaProvider)
|
||||
}
|
||||
}
|
||||
.overlay(alignment: .topTrailing) {
|
||||
@@ -61,32 +65,21 @@ struct LocationSharingScreen: View {
|
||||
|
||||
@ToolbarContentBuilder
|
||||
private var toolbar: some ToolbarContent {
|
||||
ToolbarItem(placement: context.viewState.showShareAction ? .topBarLeading : .topBarTrailing) {
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
ToolbarButton(role: .close) {
|
||||
context.send(viewAction: .close)
|
||||
}
|
||||
}
|
||||
|
||||
if context.viewState.showShareAction {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
Button {
|
||||
context.showShareSheet = true
|
||||
} label: {
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
}
|
||||
.popover(isPresented: $context.showShareSheet) { shareSheet }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var mapOptions: MapLibreMapView.Options {
|
||||
var annotations: [String: LocationAnnotation] = [:]
|
||||
if !context.viewState.isLocationPickerMode {
|
||||
let id = context.viewState.shownUserProfile?.userID ?? UUID().uuidString
|
||||
let id = context.viewState.staticLocationMarkerUserProfile?.userID ?? UUID().uuidString
|
||||
let annotation = LocationAnnotation(id: id,
|
||||
coordinate: context.viewState.initialMapCenter,
|
||||
anchorPoint: .bottomCenter) {
|
||||
LocationMarkerView(userProfile: context.viewState.shownUserProfile, mediaProvider: context.mediaProvider)
|
||||
LocationMarkerView(userProfile: context.viewState.staticLocationMarkerUserProfile, mediaProvider: context.mediaProvider)
|
||||
}
|
||||
annotations[id] = annotation
|
||||
}
|
||||
@@ -135,7 +128,7 @@ struct LocationSharingScreen: View {
|
||||
@ViewBuilder
|
||||
private var shareSheet: some View {
|
||||
let location = context.viewState.initialMapCenter
|
||||
let senderName = context.viewState.shownUserProfile?.displayName
|
||||
let senderName = context.viewState.staticLocationMarkerUserProfile?.displayName ?? context.viewState.staticLocationMarkerUserProfile?.userID
|
||||
AppActivityView(activityItems: [ShareToMapsAppActivity.MapsAppType.apple.activityURL(for: location, senderName: senderName)],
|
||||
applicationActivities: ShareToMapsAppActivity.MapsAppType.allCases.map { ShareToMapsAppActivity(type: $0, location: location, senderName: senderName) })
|
||||
.edgesIgnoringSafeArea(.bottom)
|
||||
@@ -151,95 +144,16 @@ struct LocationSharingScreen: View {
|
||||
.sheet
|
||||
}
|
||||
}
|
||||
|
||||
@State private var sharingOptionsSheetHeight: CGFloat = .zero
|
||||
|
||||
private var sharingOptionsSheet: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Button {
|
||||
context.send(viewAction: .selectLocation)
|
||||
} label: {
|
||||
if context.viewState.isSharingUserLocation {
|
||||
LocationSharingLabel(text: L10n.screenShareMyLocationAction,
|
||||
icon: \.locationNavigatorCentred,
|
||||
iconColor: .compound.iconSecondary)
|
||||
} else {
|
||||
LocationSharingLabel(text: L10n.screenShareThisLocationAction,
|
||||
icon: \.locationNavigator,
|
||||
iconColor: .compound.iconSecondary)
|
||||
}
|
||||
}
|
||||
if context.viewState.showLiveLocationSharingButton {
|
||||
Button { } label: {
|
||||
LocationSharingLabel(text: L10n.actionShareLiveLocation,
|
||||
icon: \.locationPinSolid,
|
||||
iconColor: .compound.iconAccentPrimary)
|
||||
}
|
||||
}
|
||||
}
|
||||
.font(.compound.bodyLG)
|
||||
.foregroundStyle(.compound.textPrimary)
|
||||
.padding(.top, 38)
|
||||
.readHeight($sharingOptionsSheetHeight)
|
||||
.interactiveDismissDisabled()
|
||||
.presentationBackground(.compound.bgCanvasDefault)
|
||||
.presentationBackgroundInteraction(.enabled)
|
||||
.presentationDragIndicator(.hidden)
|
||||
.presentationDetents([.height(sharingOptionsSheetHeight)])
|
||||
}
|
||||
}
|
||||
|
||||
private struct LocationSharingLabel: View {
|
||||
let text: String
|
||||
let icon: KeyPath<CompoundIcons, Image>
|
||||
let iconColor: Color
|
||||
|
||||
var body: some View {
|
||||
Label {
|
||||
Text(text)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.vertical, 14)
|
||||
.rowDivider(alignment: .top, horizontalInsets: 16.0)
|
||||
} icon: {
|
||||
CompoundIcon(icon)
|
||||
.foregroundStyle(iconColor)
|
||||
}
|
||||
.padding(.leading, 16)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct LocationSharingScreen_Previews: PreviewProvider, TestablePreview {
|
||||
static let viewModel = LocationSharingScreenViewModel(interactionMode: .viewStatic(senderID: "@dan:matrix.org", geoURI: .init(latitude: 41.9027835,
|
||||
longitude: 12.4963655)),
|
||||
mapURLBuilder: ServiceLocator.shared.settings.mapTilerConfiguration,
|
||||
liveLocationSharingEnabled: true,
|
||||
roomProxy: JoinedRoomProxyMock(.init()),
|
||||
timelineController: MockTimelineController(),
|
||||
analytics: ServiceLocator.shared.analytics,
|
||||
userIndicatorController: UserIndicatorControllerMock(),
|
||||
mediaProvider: MediaProviderMock(configuration: .init()))
|
||||
static let viewModel = LocationSharingScreenViewModel.mock(type: .staticSenderLocation)
|
||||
|
||||
static let pinViewModel = LocationSharingScreenViewModel(interactionMode: .viewStatic(senderID: nil, geoURI: .init(latitude: 41.9027835,
|
||||
longitude: 12.4963655)),
|
||||
mapURLBuilder: ServiceLocator.shared.settings.mapTilerConfiguration,
|
||||
liveLocationSharingEnabled: true,
|
||||
roomProxy: JoinedRoomProxyMock(.init()),
|
||||
timelineController: MockTimelineController(),
|
||||
analytics: ServiceLocator.shared.analytics,
|
||||
userIndicatorController: UserIndicatorControllerMock(),
|
||||
mediaProvider: MediaProviderMock(configuration: .init()))
|
||||
static let pinViewModel = LocationSharingScreenViewModel.mock(type: .staticPinLocation)
|
||||
|
||||
static let pickerViewModel = LocationSharingScreenViewModel(interactionMode: .picker,
|
||||
mapURLBuilder: ServiceLocator.shared.settings.mapTilerConfiguration,
|
||||
liveLocationSharingEnabled: true,
|
||||
roomProxy: JoinedRoomProxyMock(.init()),
|
||||
timelineController: MockTimelineController(),
|
||||
analytics: ServiceLocator.shared.analytics,
|
||||
userIndicatorController: UserIndicatorControllerMock(),
|
||||
mediaProvider: MediaProviderMock(configuration: .init()))
|
||||
static let pickerViewModel = LocationSharingScreenViewModel.mock(type: .picker)
|
||||
|
||||
static var previews: some View {
|
||||
ElementNavigationStack {
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
//
|
||||
// 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 StaticLocationSheet: View {
|
||||
@Bindable var context: LocationSharingScreenViewModel.Context
|
||||
@State private var height = CGFloat.zero
|
||||
|
||||
var body: some View {
|
||||
mainContent
|
||||
.readHeight($height)
|
||||
.interactiveDismissDisabled()
|
||||
.presentationBackground(.compound.bgCanvasDefault)
|
||||
.presentationBackgroundInteraction(.enabled)
|
||||
.presentationDragIndicator(.hidden)
|
||||
.presentationDetents([.height(height)])
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var mainContent: some View {
|
||||
if case let .viewStatic(location) = context.viewState.interactionMode,
|
||||
let userProfile = context.viewState.userProfile {
|
||||
VStack(spacing: 0) {
|
||||
Text(L10n.screenStaticLocationSheetTitle)
|
||||
.foregroundStyle(.compound.textPrimary)
|
||||
.font(.compound.bodyLGSemibold)
|
||||
.padding(.top, 29)
|
||||
.padding(.bottom, 25)
|
||||
Button {
|
||||
context.showShareSheet = true
|
||||
} label: {
|
||||
UserLocationCell(profile: userProfile,
|
||||
isOwnUser: userProfile.userID == context.viewState.ownUserID,
|
||||
isUserLocation: location.kind == .sender,
|
||||
timestamp: location.timestamp,
|
||||
mediaProvider: context.mediaProvider)
|
||||
}
|
||||
accessibilityHint(L10n.actionShare)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This may be reused for live location sharing sheet in the future with some tweaks
|
||||
private struct UserLocationCell: View {
|
||||
let profile: UserProfileProxy
|
||||
let isOwnUser: Bool
|
||||
let isUserLocation: Bool
|
||||
let timestamp: Date
|
||||
var mediaProvider: MediaProviderProtocol?
|
||||
|
||||
private var name: String {
|
||||
isOwnUser ? L10n.commonYou : profile.displayName ?? profile.userID
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 12) {
|
||||
LoadableAvatarImage(url: profile.avatarURL,
|
||||
name: profile.displayName,
|
||||
contentID: profile.id,
|
||||
avatarSize: .user(on: .map),
|
||||
mediaProvider: mediaProvider)
|
||||
.accessibilityHidden(true)
|
||||
|
||||
HStack(spacing: 12) {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text(name)
|
||||
.font(.compound.bodyLG)
|
||||
.foregroundStyle(.compound.textPrimary)
|
||||
HStack(spacing: 4) {
|
||||
CompoundIcon(isUserLocation ? \.locationNavigatorCentred : \.locationNavigator,
|
||||
size: .xSmall,
|
||||
relativeTo: .compound.bodyMD)
|
||||
.foregroundStyle(.compound.iconSecondary)
|
||||
.accessibilityLabel(isUserLocation ? L10n.a11ySenderLocation : L10n.a11yPinnedLocation)
|
||||
Text(L10n.screenStaticLocationSheetTimestampDescription(timestamp.formatted(.relative(presentation: .named))))
|
||||
.font(.compound.bodyMD)
|
||||
.foregroundStyle(.compound.textSecondary)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
CompoundIcon(\.shareIos)
|
||||
.foregroundStyle(.compound.iconSecondary)
|
||||
.accessibilityHidden(true)
|
||||
}
|
||||
.padding(.vertical, 12)
|
||||
.rowDivider(alignment: .top)
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.accessibilityElement(children: .combine)
|
||||
}
|
||||
}
|
||||
|
||||
struct StaticLocationSheet_Previews: PreviewProvider, TestablePreview {
|
||||
static let viewModel = LocationSharingScreenViewModel.mock(type: .staticSenderLocation, senderID: RoomMemberProxyMock.mockMe.userID)
|
||||
|
||||
static let pinViewModel = LocationSharingScreenViewModel.mock(type: .staticPinLocation)
|
||||
|
||||
static var previews: some View {
|
||||
StaticLocationSheet(context: viewModel.context)
|
||||
.previewDisplayName("Static own location")
|
||||
StaticLocationSheet(context: pinViewModel.context)
|
||||
.previewDisplayName("Static pin location")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user