From 2e9a499fc38fbc818927158fc183eebdc44dd60d Mon Sep 17 00:00:00 2001 From: Mauro <34335419+Velin92@users.noreply.github.com> Date: Fri, 17 Apr 2026 13:13:16 +0200 Subject: [PATCH] LLS Sheet implementation (#5420) * 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 --- .../Sources/GeneratedAccessibilityTests.swift | 8 ++ ElementX.xcodeproj/project.pbxproj | 83 +++++++------ .../en-US.lproj/Localizable.strings | 13 +-- .../en.lproj/Localizable.strings | 13 +-- .../en.lproj/Localizable.stringsdict | 16 +++ ElementX/Sources/Generated/Strings.swift | 28 ++--- .../Extensions/CLLocationCoordinate2D.swift | 14 +++ .../Other/MapLibre/MapLibreMapView.swift | 16 ++- .../Other/SwiftUI/Views/StopButton.swift | 23 ++++ .../TestablePreviewsDictionary.swift | 2 + .../LocationSharingScreenModels.swift | 4 + .../LocationSharingScreenViewModel.swift | 51 +++++++- .../View/LiveLocationSheet.swift | 96 +++++++++++++++ .../View/LocationSharingScreen.swift | 7 +- .../View/StaticLocationSheet.swift | 66 ++--------- .../View/UserLocationCell.swift | 109 ++++++++++++++++++ .../View/DeveloperOptionsScreen.swift | 1 + .../LiveLocationRoomTimelineView.swift | 10 +- .../Location/LiveLocationShareProxy.swift | 6 +- .../Sources/GeneratedPreviewTests.swift | 16 +++ ...ionRoomTimelineView.Bubbles-iPad-en-GB.png | 4 +- ...onRoomTimelineView.Bubbles-iPad-pseudo.png | 4 +- ...nRoomTimelineView.Bubbles-iPhone-en-GB.png | 4 +- ...RoomTimelineView.Bubbles-iPhone-pseudo.png | 4 +- ...LocationSheet.Live-location-iPad-en-GB.png | 3 + ...ocationSheet.Live-location-iPad-pseudo.png | 3 + ...cationSheet.Live-location-iPhone-en-GB.png | 3 + ...ationSheet.Live-location-iPhone-pseudo.png | 3 + ...et.Live-locations-are-empty-iPad-en-GB.png | 3 + ...t.Live-locations-are-empty-iPad-pseudo.png | 3 + ....Live-locations-are-empty-iPhone-en-GB.png | 3 + ...Live-locations-are-empty-iPhone-pseudo.png | 3 + ...onSheet.Static-own-location-iPad-en-GB.png | 4 +- ...nSheet.Static-own-location-iPad-pseudo.png | 4 +- ...Sheet.Static-own-location-iPhone-en-GB.png | 4 +- ...heet.Static-own-location-iPhone-pseudo.png | 4 +- ...onSheet.Static-pin-location-iPad-en-GB.png | 4 +- ...nSheet.Static-pin-location-iPad-pseudo.png | 4 +- ...Sheet.Static-pin-location-iPhone-en-GB.png | 4 +- ...heet.Static-pin-location-iPhone-pseudo.png | 4 +- ...rLocationCell.Live-location-iPad-en-GB.png | 3 + ...LocationCell.Live-location-iPad-pseudo.png | 3 + ...ocationCell.Live-location-iPhone-en-GB.png | 3 + ...cationCell.Live-location-iPhone-pseudo.png | 3 + ...ionCell.Static-pin-location-iPad-en-GB.png | 3 + ...onCell.Static-pin-location-iPad-pseudo.png | 3 + ...nCell.Static-pin-location-iPhone-en-GB.png | 3 + ...Cell.Static-pin-location-iPhone-pseudo.png | 3 + ...ionCell.Stiatc-user-locaton-iPad-en-GB.png | 3 + ...onCell.Stiatc-user-locaton-iPad-pseudo.png | 3 + ...nCell.Stiatc-user-locaton-iPhone-en-GB.png | 3 + ...Cell.Stiatc-user-locaton-iPhone-pseudo.png | 3 + 52 files changed, 518 insertions(+), 172 deletions(-) create mode 100644 ElementX/Sources/Other/Extensions/CLLocationCoordinate2D.swift create mode 100644 ElementX/Sources/Other/SwiftUI/Views/StopButton.swift create mode 100644 ElementX/Sources/Screens/LocationSharing/View/LiveLocationSheet.swift create mode 100644 ElementX/Sources/Screens/LocationSharing/View/UserLocationCell.swift create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-location-iPad-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-location-iPad-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-location-iPhone-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-location-iPhone-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-locations-are-empty-iPad-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-locations-are-empty-iPad-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-locations-are-empty-iPhone-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-locations-are-empty-iPhone-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Live-location-iPad-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Live-location-iPad-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Live-location-iPhone-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Live-location-iPhone-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Static-pin-location-iPad-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Static-pin-location-iPad-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Static-pin-location-iPhone-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Static-pin-location-iPhone-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Stiatc-user-locaton-iPad-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Stiatc-user-locaton-iPad-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Stiatc-user-locaton-iPhone-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Stiatc-user-locaton-iPhone-pseudo.png diff --git a/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift b/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift index b7ca2dfab..b090d8304 100644 --- a/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift +++ b/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift @@ -311,6 +311,10 @@ extension AccessibilityTests { try await performAccessibilityAudit(named: "LiveLocationSharingBannerView_Previews") } + func testLiveLocationSheet() async throws { + try await performAccessibilityAudit(named: "LiveLocationSheet_Previews") + } + func testLoadableImage() async throws { try await performAccessibilityAudit(named: "LoadableImage_Previews") } @@ -827,6 +831,10 @@ extension AccessibilityTests { try await performAccessibilityAudit(named: "UserIndicatorToastView_Previews") } + func testUserLocationCell() async throws { + try await performAccessibilityAudit(named: "UserLocationCell_Previews") + } + func testUserProfileCell() async throws { try await performAccessibilityAudit(named: "UserProfileCell_Previews") } diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index b909d6875..fb08872af 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 77; + objectVersion = 63; objects = { /* Begin PBXAggregateTarget section */ @@ -667,6 +667,7 @@ 708FC3184CCED825F0A36273 /* RoomThreadListServiceProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCE06F4A71FD46C9D8CD432E /* RoomThreadListServiceProxy.swift */; }; 709A9B52FC26B4CB86B8B020 /* EncryptedRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F06F70B9C433BAD4BC6B9F5 /* EncryptedRoomTimelineView.swift */; }; 70B83D44043293B4B77440B9 /* PollFormScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB63761D9F9CE8B23CBD6179 /* PollFormScreenModels.swift */; }; + 715FB2D768E8919979EACD71 /* StopButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A786DE47C0DBCE0CD163193 /* StopButton.swift */; }; 71643093F87153F633A1B025 /* ThreadDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA4F09CB613C54FDC73AE6A /* ThreadDecorator.swift */; }; 719E7AAD1F8E68F68F30FECD /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = A40C19719687984FD9478FBE /* Task.swift */; }; 71AC1CAAC23403FFE847F2C9 /* ComposerToolbarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90514BE9B8ACCBCF0AD2489 /* ComposerToolbarViewModel.swift */; }; @@ -1004,8 +1005,10 @@ A6DEC1ADEC8FEEC206A0FA37 /* AttributedStringBuilderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.swift */; }; A6F345328CCC5C9B0DAE2257 /* LogViewerScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BB05221D7D941CC82DC8480 /* LogViewerScreenViewModel.swift */; }; A6FFC4C5154C446BAD6B40D8 /* TimelineItemProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8520AFD6680CBAD388F6D927 /* TimelineItemProvider.swift */; }; + A70C429B2F8FD7B600B4EEF8 /* LiveLocationSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70C429A2F8FD7B000B4EEF8 /* LiveLocationSheet.swift */; }; A722F426FD81FC67706BB1E0 /* CustomLayoutLabelStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42236480CF0431535EBE8387 /* CustomLayoutLabelStyle.swift */; }; A74438ED16F8683A4B793E6A /* AnalyticsSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BCE3FAF40932AC7C7639AC4 /* AnalyticsSettingsScreenViewModel.swift */; }; + A76917EC2F923CBD00601632 /* CLLocationCoordinate2D.swift in Sources */ = {isa = PBXBuildFile; fileRef = A76917EB2F923CB700601632 /* CLLocationCoordinate2D.swift */; }; A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; }; A7DB75E090542331F6668A23 /* CreateRoomScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF19027E7FFA5E63D148873A /* CreateRoomScreenViewModel.swift */; }; A808DC3F72D15C6C5A52317E /* TimelineItemDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCDA016D05107DED3B9495CB /* TimelineItemDebugView.swift */; }; @@ -1013,6 +1016,7 @@ A851635B3255C6DC07034A12 /* RoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8108C8F0ACF6A7EB72D0117 /* RoomScreenCoordinator.swift */; }; A87DC550659C5176AC1829DE /* ElementTextFieldStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7673F2B0B038FAB2A8D16AD /* ElementTextFieldStyle.swift */; }; A88328D7E17F73AB64501B51 /* Flow in Frameworks */ = {isa = PBXBuildFile; productRef = 4D7E6BFC89715FC3CF0349D0 /* Flow */; }; + A8DB299C5C891EDAE74A22F4 /* UserLocationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F7482D5D1526E3399B00174 /* UserLocationCell.swift */; }; A8E324E700E596E36B0A311B /* BootDetectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F054DE7D47849687662C9D9 /* BootDetectionManager.swift */; }; A8FA7671948E3DF27F320026 /* BugReportFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7367B3B9A8CAF902220F31D1 /* BugReportFlowCoordinator.swift */; }; A91D125414C3D9ABBABCF2F1 /* KZFileWatchers in Frameworks */ = {isa = PBXBuildFile; productRef = 6690850AA47ECED7E1CAB345 /* KZFileWatchers */; }; @@ -1625,7 +1629,7 @@ 044E501B8331B339874D1B96 /* CompoundIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompoundIcon.swift; sourceTree = ""; }; 045253F9967A535EE5B16691 /* Label.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Label.swift; sourceTree = ""; }; 046C0D3F53B0B5EF0A1F5BEA /* RoomSummaryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryTests.swift; sourceTree = ""; }; - 048A21188AB19349D026BECD /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + 048A21188AB19349D026BECD /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 04BB8DDE245ED86C489BA983 /* AccessibilityIdentifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityIdentifiers.swift; sourceTree = ""; }; 04DF593C3F7AF4B2FBAEB05D /* FileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManager.swift; sourceTree = ""; }; 04EB6035C1F33F25F1EBFB7D /* RoomThreadListServiceProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomThreadListServiceProxyProtocol.swift; sourceTree = ""; }; @@ -1715,7 +1719,7 @@ 128501375217576AF0FE3E92 /* RoomAttachmentPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomAttachmentPicker.swift; sourceTree = ""; }; 12B09A94C519227264A41208 /* RoomMembershipDetailsProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembershipDetailsProxy.swift; sourceTree = ""; }; 12FD5280AF55AB7F50F8E47D /* preview_avatar_room.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = preview_avatar_room.jpg; sourceTree = ""; }; - 1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; path = IntegrationTests.xctestplan; sourceTree = ""; }; + 1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = IntegrationTests.xctestplan; sourceTree = ""; }; 130ED565A078F7E0B59D9D25 /* UNTextInputNotificationResponse+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNTextInputNotificationResponse+Creator.swift"; sourceTree = ""; }; 136F80A613B55BDD071DCEA5 /* JoinRoomScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinRoomScreenModels.swift; sourceTree = ""; }; 13802897C7AFA360EA74C0B0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; }; @@ -1736,7 +1740,7 @@ 16D09C79746BDCD9173EB3A7 /* RoomDetailsEditScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreenModels.swift; sourceTree = ""; }; 16D353E10A64172D863769BF /* TombstonedAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TombstonedAvatarImage.swift; sourceTree = ""; }; 1715E3D7F53C0748AA50C91C /* PostHogAnalyticsClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogAnalyticsClient.swift; sourceTree = ""; }; - 174E4AEF3DED300AA81046EC /* compound-ios */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "compound-ios"; path = "compound-ios"; sourceTree = SOURCE_ROOT; }; + 174E4AEF3DED300AA81046EC /* compound-ios */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "compound-ios"; sourceTree = SOURCE_ROOT; }; 17A8AA0DFA06012A9DAB951E /* TimelineProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineProxyMock.swift; sourceTree = ""; }; 17BAE25A0E9E9F2F1BBA8930 /* DeactivateAccountScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeactivateAccountScreenViewModel.swift; sourceTree = ""; }; 181CF280BC8E3F335AFCB4B8 /* RemotePreferenceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemotePreferenceTests.swift; sourceTree = ""; }; @@ -1752,6 +1756,7 @@ 1A1265FAF2C0AF1C30605BE7 /* SessionVerificationRequestDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationRequestDetailsView.swift; sourceTree = ""; }; 1A13364350970987B93F6018 /* JoinRoomByAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinRoomByAddressView.swift; sourceTree = ""; }; 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Strings+Untranslated.swift"; sourceTree = ""; }; + 1A786DE47C0DBCE0CD163193 /* StopButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StopButton.swift; sourceTree = ""; }; 1A7ED2EF5BDBAD2A7DBC4636 /* GeoURITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoURITests.swift; sourceTree = ""; }; 1A9E7C89E4BE90383BE235C5 /* cy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = cy; path = cy.lproj/Localizable.stringsdict; sourceTree = ""; }; 1B2AC540DE619B36832A5DB5 /* LocationRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationRoomTimelineItem.swift; sourceTree = ""; }; @@ -1763,7 +1768,7 @@ 1B9D191A81FFB0C72CE73E77 /* RoomSelectionScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSelectionScreenModels.swift; sourceTree = ""; }; 1BA5A62DA4B543827FF82354 /* LAContextMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LAContextMock.swift; sourceTree = ""; }; 1BA8082E26C77A2C587B34B3 /* MockTimelineController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTimelineController.swift; sourceTree = ""; }; - 1BC752C2A4606C4C2D1ADB41 /* 94 */ = {isa = PBXFileReference; path = 94; sourceTree = ""; }; + 1BC752C2A4606C4C2D1ADB41 /* 94 */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = 94; sourceTree = ""; }; 1C21A715237F2B6D6E80998C /* SecureBackupControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupControllerProtocol.swift; sourceTree = ""; }; 1C25B6EBEB414431187D73B7 /* TimelineReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReplyView.swift; sourceTree = ""; }; 1C78111573987B1D79ED0868 /* LinkMetadataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkMetadataProvider.swift; sourceTree = ""; }; @@ -1828,7 +1833,7 @@ 25E7E9B7FEAB6169D960C206 /* QRCodeLoginScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeLoginScreenViewModelTests.swift; sourceTree = ""; }; 25F8664F1FB95AF3C4202478 /* PollFormScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollFormScreenCoordinator.swift; sourceTree = ""; }; 260004737C573A56FA01E86E /* Encodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Encodable.swift; sourceTree = ""; }; - 267BB1D5B08A9511F894CB57 /* PreviewTests.xctestplan */ = {isa = PBXFileReference; path = PreviewTests.xctestplan; sourceTree = ""; }; + 267BB1D5B08A9511F894CB57 /* PreviewTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = PreviewTests.xctestplan; sourceTree = ""; }; 26B0A96B8FE4849227945067 /* VoiceMessageRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRecorder.swift; sourceTree = ""; }; 26EAAB54C6CE91D64B69A9F8 /* AppLockServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockServiceProtocol.swift; sourceTree = ""; }; 2711E5996016ABD6EAAEB58A /* LogLevel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogLevel.swift; sourceTree = ""; }; @@ -1853,7 +1858,7 @@ 29A953B6C0C431DBF4DD00B4 /* RoomSummary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummary.swift; sourceTree = ""; }; 2A2BB38DF61F5100B8723112 /* TimelineMediaPreviewModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMediaPreviewModels.swift; sourceTree = ""; }; 2A5C6FBF97B6EED3D4FA5EFF /* AttributedStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringBuilder.swift; sourceTree = ""; }; - 2A7BE2B89310058659E6F459 /* accountsV2 */ = {isa = PBXFileReference; path = accountsV2; sourceTree = ""; }; + 2A7BE2B89310058659E6F459 /* accountsV2 */ = {isa = PBXFileReference; lastKnownFileType = file; path = accountsV2; sourceTree = ""; }; 2A95C9B8299A36A6495DECA6 /* TracingHook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingHook.swift; sourceTree = ""; }; 2AB2C848BB9A7A9B618B7B89 /* TextBasedRoomTimelineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextBasedRoomTimelineTests.swift; sourceTree = ""; }; 2ADF12A50186B75C68017B61 /* DeclineAndBlockScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeclineAndBlockScreenViewModelTests.swift; sourceTree = ""; }; @@ -1913,7 +1918,7 @@ 358528B29FA72ACFD0D9644B /* SpacesScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpacesScreenCoordinator.swift; sourceTree = ""; }; 35A057BA9BE0F079784CD061 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 35AFCF4C05DEED04E3DB1A16 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; - 36DA824791172B9821EACBED /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + 36DA824791172B9821EACBED /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 36FD673E24FBFCFDF398716A /* RoomMemberProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberProxyMock.swift; sourceTree = ""; }; 3747C96188856006F784BF49 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ko; path = ko.lproj/Localizable.stringsdict; sourceTree = ""; }; 37A63A59BFDDC494B1C20119 /* CallScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallScreenViewModel.swift; sourceTree = ""; }; @@ -1960,6 +1965,7 @@ 3E9E0929CEFA356090BE5FB8 /* RoomMembersListScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenViewModelTests.swift; sourceTree = ""; }; 3EF1AC723C2609C7705569CA /* MediaLoaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaLoaderTests.swift; sourceTree = ""; }; 3F54FA7C5CB7B342EF9B9B2F /* NoticeRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineView.swift; sourceTree = ""; }; + 3F7482D5D1526E3399B00174 /* UserLocationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserLocationCell.swift; sourceTree = ""; }; 40076C770A5FB83325252973 /* VoiceMessageMediaManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageMediaManager.swift; sourceTree = ""; }; 4048547AC50ADCF201684E87 /* EditRoomAddressScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditRoomAddressScreen.swift; sourceTree = ""; }; 406C90AF8C3E98DF5D4E5430 /* ElementCallServiceConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallServiceConstants.swift; sourceTree = ""; }; @@ -2035,7 +2041,7 @@ 4A5B4CD611DE7E94F5BA87B2 /* AppLockTimerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockTimerTests.swift; sourceTree = ""; }; 4AB29A2D95D3469B5F016655 /* SecureBackupControllerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupControllerMock.swift; sourceTree = ""; }; 4AC3F28DECDF8665E8EBC76E /* ClassicAppMediaLoaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClassicAppMediaLoaderTests.swift; sourceTree = ""; }; - 4B1F71AC585827E6C416C15A /* AppIcon.icon */ = {isa = PBXFileReference; path = AppIcon.icon; sourceTree = ""; }; + 4B1F71AC585827E6C416C15A /* AppIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIcon.icon; sourceTree = ""; }; 4B2B564CA6570E1487A7C7CC /* SpaceRoomListProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceRoomListProxy.swift; sourceTree = ""; }; 4B2D4EEBE8C098BBADD10939 /* SecureBackupKeyBackupScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupKeyBackupScreenCoordinator.swift; sourceTree = ""; }; 4B41FABA2B0AEF4389986495 /* LoginMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginMode.swift; sourceTree = ""; }; @@ -2397,7 +2403,7 @@ 8D55702474F279D910D2D162 /* RoomStateEventStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomStateEventStringBuilder.swift; sourceTree = ""; }; 8D8169443E5AC5FF71BFB3DB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = ""; }; 8DA1E8F287680C8ED25EDBAC /* NetworkMonitorMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitorMock.swift; sourceTree = ""; }; - 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; path = UITests.xctestplan; sourceTree = ""; }; + 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UITests.xctestplan; sourceTree = ""; }; 8E1584F8BCF407BB94F48F04 /* EncryptionResetPasswordScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetPasswordScreen.swift; sourceTree = ""; }; 8E97CF050B0168F3D605F0E9 /* InviteUsersConfirmationSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersConfirmationSheetView.swift; sourceTree = ""; }; 8EAF4A49F3ACD8BB8B0D2371 /* ClientSDKMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientSDKMock.swift; sourceTree = ""; }; @@ -2530,8 +2536,10 @@ A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogConfiguration.swift; sourceTree = ""; }; A6C11AD9813045E44F950410 /* ElementCallWidgetDriverProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallWidgetDriverProtocol.swift; sourceTree = ""; }; A6EA0D8B0BBD8805F7D5A133 /* TextBasedRoomTimelineViewProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextBasedRoomTimelineViewProtocol.swift; sourceTree = ""; }; + A70C429A2F8FD7B000B4EEF8 /* LiveLocationSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveLocationSheet.swift; sourceTree = ""; }; A73A07BAEDD74C48795A996A /* AsyncSequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncSequence.swift; sourceTree = ""; }; A768CA51A59B8A5D8C8FD599 /* AuthenticationStartScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationStartScreen.swift; sourceTree = ""; }; + A76917EB2F923CB700601632 /* CLLocationCoordinate2D.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLLocationCoordinate2D.swift; sourceTree = ""; }; A7978C9EFBDD7DE39BD86726 /* RestorationTokenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestorationTokenTests.swift; sourceTree = ""; }; A7A1B80FE6E3BA72F9C748AD /* AdvancedSettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsScreenViewModel.swift; sourceTree = ""; }; A7C4EA55DA62F9D0F984A2AE /* CollapsibleTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleTimelineItem.swift; sourceTree = ""; }; @@ -2555,7 +2563,7 @@ AAD8234D0E9C9B12BF9F240B /* LocationAnnotation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationAnnotation.swift; sourceTree = ""; }; AB07F03461023BC39C730922 /* PhishingDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhishingDetector.swift; sourceTree = ""; }; AB26D5444A4A7E095222DE8B /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.stringsdict"; sourceTree = ""; }; - AB389C38BD41EB3E47092CFB /* AccessibilityTests.xctestplan */ = {isa = PBXFileReference; path = AccessibilityTests.xctestplan; sourceTree = ""; }; + AB389C38BD41EB3E47092CFB /* AccessibilityTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = AccessibilityTests.xctestplan; sourceTree = ""; }; ABA4CF2F5B4F68D02E412004 /* ServerConfirmationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenViewModelProtocol.swift; sourceTree = ""; }; ABF84AA68B2B7584D9275769 /* VoiceMessageTrashButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageTrashButton.swift; sourceTree = ""; }; AC0275CEE9CA078B34028BDF /* AppLockScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockScreenViewModelTests.swift; sourceTree = ""; }; @@ -2624,7 +2632,7 @@ B53AC78E49A297AC1D72A7CF /* AppMediator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppMediator.swift; sourceTree = ""; }; B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableAvatarImage.swift; sourceTree = ""; }; B5D829FD8958376614504B18 /* TargetConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TargetConfiguration.swift; sourceTree = ""; }; - B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; path = ConfettiScene.scn; sourceTree = ""; }; + B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = ConfettiScene.scn; sourceTree = ""; }; B6404166CBF5CC88673FF9E2 /* RoomDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetails.swift; sourceTree = ""; }; B65DDCF8E41759890355ACBC /* AuthenticationStartScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationStartScreenViewModelProtocol.swift; sourceTree = ""; }; B682FE2C44C5E163E7023B05 /* CopyTextButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyTextButton.swift; sourceTree = ""; }; @@ -2658,7 +2666,7 @@ BA40B98B098B6F0371B750B3 /* TemplateScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreenModels.swift; sourceTree = ""; }; BA919F521E9F0EE3638AFC85 /* BugReportScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreen.swift; sourceTree = ""; }; BB284643AF7AB131E307DCE0 /* AudioSessionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioSessionProtocol.swift; sourceTree = ""; }; - BB576F4118C35E6B5124FA22 /* test_apple_image.heic */ = {isa = PBXFileReference; path = test_apple_image.heic; sourceTree = ""; }; + BB576F4118C35E6B5124FA22 /* test_apple_image.heic */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_apple_image.heic; sourceTree = ""; }; BB5B00A014307CE37B2812CD /* TimelineViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineViewModelProtocol.swift; sourceTree = ""; }; BB6ED50FE104992419310EEB /* NotificationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHandler.swift; sourceTree = ""; }; BB8BC4C791D0E88CFCF4E5DF /* ServerSelectionScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionScreenCoordinator.swift; sourceTree = ""; }; @@ -2765,7 +2773,7 @@ CDB3227C7A74B734924942E9 /* RoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProvider.swift; sourceTree = ""; }; CDE3F3911FF7CC639BDE5844 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; CEE20623EB4A9B88FB29F2BA /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/SAS.strings; sourceTree = ""; }; - CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; path = UnitTests.xctestplan; sourceTree = ""; }; + CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = ""; }; CF19027E7FFA5E63D148873A /* CreateRoomScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomScreenViewModel.swift; sourceTree = ""; }; CF847A34FC4C8C937CD39E08 /* LabsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabsScreenViewModelProtocol.swift; sourceTree = ""; }; CFFA5E881D281810AB428EA3 /* RoomPowerLevelsProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPowerLevelsProxy.swift; sourceTree = ""; }; @@ -2839,7 +2847,7 @@ DC0AEA686E425F86F6BA0404 /* UNNotification+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNNotification+Creator.swift"; sourceTree = ""; }; DC10CCC8D68B863E20660DBC /* MessageForwardingScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenViewModelProtocol.swift; sourceTree = ""; }; DC528B3764E3CF7FCFEF40E7 /* PollInteractionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollInteractionHandler.swift; sourceTree = ""; }; - DCA2D836BD10303F37FAAEED /* test_voice_message.m4a */ = {isa = PBXFileReference; path = test_voice_message.m4a; sourceTree = ""; }; + DCA2D836BD10303F37FAAEED /* test_voice_message.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_voice_message.m4a; sourceTree = ""; }; DCAC01A97A43BE07B9E94E43 /* ShareExtensionModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareExtensionModels.swift; sourceTree = ""; }; DCDAB580109C09A6AA97AF7E /* PollFormScreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollFormScreenTests.swift; sourceTree = ""; }; DCF239C619971FDE48132550 /* SecureBackupLogoutConfirmationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupLogoutConfirmationScreenModels.swift; sourceTree = ""; }; @@ -2885,7 +2893,7 @@ E5272BC4A60B6AD7553BACA1 /* BlurHashDecode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurHashDecode.swift; sourceTree = ""; }; E53BFB7E4F329621C844E8C3 /* AnalyticsPromptScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreen.swift; sourceTree = ""; }; E55B5EA766E89FF1F87C3ACB /* RoomNotificationSettingsProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsProxyProtocol.swift; sourceTree = ""; }; - E5E7D4EE7CA295E5039FDA21 /* portrait_test_video.mp4 */ = {isa = PBXFileReference; path = portrait_test_video.mp4; sourceTree = ""; }; + E5E7D4EE7CA295E5039FDA21 /* portrait_test_video.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = portrait_test_video.mp4; sourceTree = ""; }; E5E94DCFEE803E5ABAE8ACCE /* KeychainControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainControllerProtocol.swift; sourceTree = ""; }; E5F2B6443D1ED8602F328539 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ru; path = ru.lproj/Localizable.stringsdict; sourceTree = ""; }; E5FDFAA04174CC99FB66391C /* EditRoomAddressScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditRoomAddressScreenViewModel.swift; sourceTree = ""; }; @@ -2937,7 +2945,7 @@ ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = ""; }; ED25719E19B205B668FDACFF /* UserToInvite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserToInvite.swift; sourceTree = ""; }; ED33988DA4FD4FC666800106 /* SessionVerificationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationScreenViewModel.swift; sourceTree = ""; }; - ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; path = message.caf; sourceTree = ""; }; + ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = message.caf; sourceTree = ""; }; ED49073BB1C1FC649DAC2CCD /* LocationRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationRoomTimelineView.swift; sourceTree = ""; }; ED60E4D2CD678E1EBF16F77A /* BlockedUsersScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUsersScreen.swift; sourceTree = ""; }; EDDE826EAB1BAB80C1104980 /* SpaceFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceFlowCoordinator.swift; sourceTree = ""; }; @@ -3834,6 +3842,7 @@ AEB5FF7A09B79B0C6B528F7C /* SFNumberedListView.swift */, CB04B2D794885025DACFCEFB /* SnapshotableGlassEffect.swift */, A8558D41DD4B553A752C868A /* StackedAvatarsView.swift */, + 1A786DE47C0DBCE0CD163193 /* StopButton.swift */, E10765FBC83B34A3BC4ADB23 /* TimelineScrollToBottomButton.swift */, 16D353E10A64172D863769BF /* TombstonedAvatarImage.swift */, 510DA63AB273A68D659CEC95 /* ToolbarButton.swift */, @@ -4204,6 +4213,7 @@ 44BBB96FAA2F0D53C507396B /* Extensions */ = { isa = PBXGroup; children = ( + A76917EB2F923CB700601632 /* CLLocationCoordinate2D.swift */, 981663D961C94270FA035FD0 /* Alert.swift */, 3E93A1BE7D8A2EBCAD51EEB4 /* Array.swift */, A73A07BAEDD74C48795A996A /* AsyncSequence.swift */, @@ -5715,10 +5725,12 @@ 9FD8D798D879069243A7E7F7 /* View */ = { isa = PBXGroup; children = ( + A70C429A2F8FD7B000B4EEF8 /* LiveLocationSheet.swift */, 408ACC0D28656F82A5EB6A7E /* LocationPickerSheet.swift */, B9406BD5F99685C5ABDEDD93 /* LocationShareSheet.swift */, 6F56E6E41C6DFE8054787D57 /* LocationSharingScreen.swift */, ECE03E834CC8C2721899E6AC /* StaticLocationSheet.swift */, + 3F7482D5D1526E3399B00174 /* UserLocationCell.swift */, ); path = View; sourceTree = ""; @@ -7434,7 +7446,6 @@ EE40B0E16A55BD23ECBFFD22 /* XCRemoteSwiftPackageReference "matrix-rich-text-editor-swift" */, C89CF7729E028671C5DC461E /* XCLocalSwiftPackageReference "compound-ios" */, ); - preferredProjectObjectVersion = 77; projectDirPath = ""; projectRoot = ""; targets = ( @@ -8112,6 +8123,7 @@ F8E725D42023ECA091349245 /* AudioRoomTimelineItem.swift in Sources */, 88F348E2CB14FF71CBBB665D /* AudioRoomTimelineItemContent.swift in Sources */, 7BD2123144A32F082CECC108 /* AudioRoomTimelineView.swift in Sources */, + A76917EC2F923CBD00601632 /* CLLocationCoordinate2D.swift in Sources */, 9278EC51D24E57445B290521 /* AudioSessionProtocol.swift in Sources */, D16B3134A7FF4FBA0749014A /* AuthenticationClassicAppAccountView.swift in Sources */, 90C683C87BF6D39419402E5B /* AuthenticationClassicAppBackupInstructionsView.swift in Sources */, @@ -8921,6 +8933,7 @@ 1743EAB45DA264AAFABDD3EF /* StaticLocationSheet.swift in Sources */, C58E305C380D3ADDF7912180 /* StickerRoomTimelineItem.swift in Sources */, 197441F1EF23A5DABACCA79F /* StickerRoomTimelineView.swift in Sources */, + 715FB2D768E8919979EACD71 /* StopButton.swift in Sources */, 2F94054F50E312AF30BE07F3 /* String.swift in Sources */, 7640A4B412CACF15D143CCD4 /* Strings+SAS.swift in Sources */, A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */, @@ -9043,6 +9056,7 @@ A14A9419105A1CD42F0511C4 /* UserIndicatorModalView.swift in Sources */, 9E838A62918E47BC72D6640D /* UserIndicatorPresenter.swift in Sources */, F7BC744FFA7FE248FAE7F570 /* UserIndicatorToastView.swift in Sources */, + A8DB299C5C891EDAE74A22F4 /* UserLocationCell.swift in Sources */, E3291AD16D7A5CB14781819C /* UserNotificationCenterProtocol.swift in Sources */, 2932570AA418974979D16DED /* UserPreference.swift in Sources */, 80DEA2A4B20F9E279EAE6B2B /* UserProfile+Mock.swift in Sources */, @@ -9051,6 +9065,7 @@ 1A3B073568D1DC8F76F1F3A0 /* UserProfileScreen.swift in Sources */, 6AEB650311F694A5702255C9 /* UserProfileScreenCoordinator.swift in Sources */, D4CB979EB4FE26AAD9F9A72B /* UserProfileScreenModels.swift in Sources */, + A70C429B2F8FD7B600B4EEF8 /* LiveLocationSheet.swift in Sources */, AC1DB27A4134470846BE49F6 /* UserProfileScreenViewModel.swift in Sources */, 46A183C6125A669AEB005699 /* UserProfileScreenViewModelProtocol.swift in Sources */, 69A9B430397C15075D86193F /* UserPropertiesExt.swift in Sources */, @@ -9399,9 +9414,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - OTHER_SWIFT_FLAGS = ( - "-DRELEASE", - ); + OTHER_SWIFT_FLAGS = "-DRELEASE"; PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.accessibility.tests"; PRODUCT_NAME = AccessibilityTests; SDKROOT = iphoneos; @@ -9420,9 +9433,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - OTHER_SWIFT_FLAGS = ( - "-DDEBUG", - ); + OTHER_SWIFT_FLAGS = "-DDEBUG"; PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.accessibility.tests"; PRODUCT_NAME = AccessibilityTests; SDKROOT = iphoneos; @@ -9444,9 +9455,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = "$(MARKETING_VERSION)"; - OTHER_SWIFT_FLAGS = ( - "-DIS_NSE", - ); + OTHER_SWIFT_FLAGS = "-DIS_NSE"; PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.nse"; PRODUCT_DISPLAY_NAME = "$(APP_DISPLAY_NAME)"; PRODUCT_NAME = NSE; @@ -9513,9 +9522,7 @@ "$(inherited)", "-ObjC", ); - OTHER_SWIFT_FLAGS = ( - "-DIS_MAIN_APP", - ); + OTHER_SWIFT_FLAGS = "-DIS_MAIN_APP"; PILLS_UT_TYPE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).pills"; PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)"; PRODUCT_NAME = "$(APP_NAME)"; @@ -9545,9 +9552,7 @@ "$(inherited)", "-ObjC", ); - OTHER_SWIFT_FLAGS = ( - "-DIS_MAIN_APP", - ); + OTHER_SWIFT_FLAGS = "-DIS_MAIN_APP"; PILLS_UT_TYPE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).pills"; PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)"; PRODUCT_NAME = "$(APP_NAME)"; @@ -9780,9 +9785,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - OTHER_SWIFT_FLAGS = ( - "-DDEBUG", - ); + OTHER_SWIFT_FLAGS = "-DDEBUG"; PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.ui.tests"; PRODUCT_NAME = UITests; SDKROOT = iphoneos; @@ -9801,9 +9804,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - OTHER_SWIFT_FLAGS = ( - "-DRELEASE", - ); + OTHER_SWIFT_FLAGS = "-DRELEASE"; PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.ui.tests"; PRODUCT_NAME = UITests; SDKROOT = iphoneos; @@ -9825,9 +9826,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = "$(MARKETING_VERSION)"; - OTHER_SWIFT_FLAGS = ( - "-DIS_NSE", - ); + OTHER_SWIFT_FLAGS = "-DIS_NSE"; PRODUCT_BUNDLE_IDENTIFIER = "${BASE_BUNDLE_IDENTIFIER}.nse"; PRODUCT_DISPLAY_NAME = "$(APP_DISPLAY_NAME)"; PRODUCT_NAME = NSE; diff --git a/ElementX/Resources/Localizations/en-US.lproj/Localizable.strings b/ElementX/Resources/Localizations/en-US.lproj/Localizable.strings index 37e05d1be..693c99fce 100644 --- a/ElementX/Resources/Localizations/en-US.lproj/Localizable.strings +++ b/ElementX/Resources/Localizations/en-US.lproj/Localizable.strings @@ -189,7 +189,7 @@ "common_about" = "About"; "common_acceptable_use_policy" = "Acceptable use policy"; "common_add_account" = "Add an account"; -"common_add_another_account" = "Add another account"; +"common_add_another_account" = "Add account"; "common_adding_caption" = "Adding caption"; "common_advanced_settings" = "Advanced settings"; "common_an_image" = "an image"; @@ -903,6 +903,9 @@ "screen_link_new_device_root_title" = "What type of device do you want to link?"; "screen_link_new_device_wrong_number_subtitle" = "Please try again and make sure that you’ve entered the 2-digit code correctly. If the numbers still don’t match then contact your account provider."; "screen_link_new_device_wrong_number_title" = "The numbers don’t match"; +"screen_live_location_sheet_nobody_sharing" = "Nobody is sharing their location"; +"screen_live_location_sheet_sharing_live_location" = "Sharing live location"; +"screen_live_location_sheet_title" = "On the map"; "screen_login_error_deactivated_account" = "This account has been deactivated."; "screen_login_error_invalid_credentials" = "Incorrect username and/or password"; "screen_login_error_invalid_user_id" = "This is not a valid user identifier. Expected format: ‘@user:homeserver.org’"; @@ -1417,14 +1420,6 @@ "screen_signout_save_recovery_key_title" = "Make sure you have access to your recovery key before removing this device"; "screen_space_add_room_action" = "Room"; "screen_space_add_rooms_room_access_description" = "Adding a room will not affect the room access. To change the access go to Room settings > Security & privacy."; -"screen_space_announcement_item1" = "View spaces you've created or joined"; -"screen_space_announcement_item2" = "Accept or decline invites to spaces"; -"screen_space_announcement_item3" = "Discover any rooms you can join in your spaces"; -"screen_space_announcement_item4" = "Join public spaces"; -"screen_space_announcement_item5" = "Leave any spaces you’ve joined"; -"screen_space_announcement_notice" = "Filtering, creating and managing spaces is coming soon."; -"screen_space_announcement_subtitle" = "Welcome to the beta version of Spaces! With this first version you can:"; -"screen_space_announcement_title" = "Introducing Spaces"; "screen_space_empty_state_title" = "Add your first room"; "screen_space_list_description" = "Spaces you have created or joined."; "screen_space_list_details" = "%1$@ • %2$@"; diff --git a/ElementX/Resources/Localizations/en.lproj/Localizable.strings b/ElementX/Resources/Localizations/en.lproj/Localizable.strings index 36a944a5d..93fd03ccb 100644 --- a/ElementX/Resources/Localizations/en.lproj/Localizable.strings +++ b/ElementX/Resources/Localizations/en.lproj/Localizable.strings @@ -189,7 +189,7 @@ "common_about" = "About"; "common_acceptable_use_policy" = "Acceptable use policy"; "common_add_account" = "Add an account"; -"common_add_another_account" = "Add another account"; +"common_add_another_account" = "Add account"; "common_adding_caption" = "Adding caption"; "common_advanced_settings" = "Advanced settings"; "common_an_image" = "an image"; @@ -903,6 +903,9 @@ "screen_link_new_device_root_title" = "What type of device do you want to link?"; "screen_link_new_device_wrong_number_subtitle" = "Please try again and make sure that you’ve entered the 2-digit code correctly. If the numbers still don’t match then contact your account provider."; "screen_link_new_device_wrong_number_title" = "The numbers don’t match"; +"screen_live_location_sheet_nobody_sharing" = "Nobody is sharing their location"; +"screen_live_location_sheet_sharing_live_location" = "Sharing live location"; +"screen_live_location_sheet_title" = "On the map"; "screen_login_error_deactivated_account" = "This account has been deactivated."; "screen_login_error_invalid_credentials" = "Incorrect username and/or password"; "screen_login_error_invalid_user_id" = "This is not a valid user identifier. Expected format: ‘@user:homeserver.org’"; @@ -1417,14 +1420,6 @@ "screen_signout_save_recovery_key_title" = "Make sure you have access to your recovery key before removing this device"; "screen_space_add_room_action" = "Room"; "screen_space_add_rooms_room_access_description" = "Adding a room will not affect the room access. To change the access go to Room settings > Security & privacy."; -"screen_space_announcement_item1" = "View spaces you've created or joined"; -"screen_space_announcement_item2" = "Accept or decline invites to spaces"; -"screen_space_announcement_item3" = "Discover any rooms you can join in your spaces"; -"screen_space_announcement_item4" = "Join public spaces"; -"screen_space_announcement_item5" = "Leave any spaces you’ve joined"; -"screen_space_announcement_notice" = "Filtering, creating and managing spaces is coming soon."; -"screen_space_announcement_subtitle" = "Welcome to the beta version of Spaces! With this first version you can:"; -"screen_space_announcement_title" = "Introducing Spaces"; "screen_space_empty_state_title" = "Add your first room"; "screen_space_list_description" = "Spaces you have created or joined."; "screen_space_list_details" = "%1$@ • %2$@"; diff --git a/ElementX/Resources/Localizations/en.lproj/Localizable.stringsdict b/ElementX/Resources/Localizations/en.lproj/Localizable.stringsdict index 3d26910c9..855ce9151 100644 --- a/ElementX/Resources/Localizations/en.lproj/Localizable.stringsdict +++ b/ElementX/Resources/Localizations/en.lproj/Localizable.stringsdict @@ -354,6 +354,22 @@ NSStringLocalizedFormatKey %#@COUNT@ + screen_live_location_sheet_subtitle + + COUNT + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d person + other + %1$d people + + NSStringLocalizedFormatKey + %#@COUNT@ + screen_pinned_timeline_screen_title COUNT diff --git a/ElementX/Sources/Generated/Strings.swift b/ElementX/Sources/Generated/Strings.swift index bbf9a730c..5b4063278 100644 --- a/ElementX/Sources/Generated/Strings.swift +++ b/ElementX/Sources/Generated/Strings.swift @@ -418,7 +418,7 @@ internal enum L10n { internal static var commonAcceptableUsePolicy: String { return L10n.tr("Localizable", "common_acceptable_use_policy") } /// Add an account internal static var commonAddAccount: String { return L10n.tr("Localizable", "common_add_account") } - /// Add another account + /// Add account internal static var commonAddAnotherAccount: String { return L10n.tr("Localizable", "common_add_another_account") } /// Adding caption internal static var commonAddingCaption: String { return L10n.tr("Localizable", "common_adding_caption") } @@ -2118,6 +2118,16 @@ internal enum L10n { internal static var screenLinkNewDeviceWrongNumberSubtitle: String { return L10n.tr("Localizable", "screen_link_new_device_wrong_number_subtitle") } /// The numbers don’t match internal static var screenLinkNewDeviceWrongNumberTitle: String { return L10n.tr("Localizable", "screen_link_new_device_wrong_number_title") } + /// Nobody is sharing their location + internal static var screenLiveLocationSheetNobodySharing: String { return L10n.tr("Localizable", "screen_live_location_sheet_nobody_sharing") } + /// Sharing live location + internal static var screenLiveLocationSheetSharingLiveLocation: String { return L10n.tr("Localizable", "screen_live_location_sheet_sharing_live_location") } + /// Plural format key: "%#@COUNT@" + internal static func screenLiveLocationSheetSubtitle(_ p1: Int) -> String { + return L10n.tr("Localizable", "screen_live_location_sheet_subtitle", p1) + } + /// On the map + internal static var screenLiveLocationSheetTitle: String { return L10n.tr("Localizable", "screen_live_location_sheet_title") } /// This account has been deactivated. internal static var screenLoginErrorDeactivatedAccount: String { return L10n.tr("Localizable", "screen_login_error_deactivated_account") } /// Incorrect username and/or password @@ -3283,22 +3293,6 @@ internal enum L10n { internal static var screenSpaceAddRoomAction: String { return L10n.tr("Localizable", "screen_space_add_room_action") } /// Adding a room will not affect the room access. To change the access go to Room settings > Security & privacy. internal static var screenSpaceAddRoomsRoomAccessDescription: String { return L10n.tr("Localizable", "screen_space_add_rooms_room_access_description") } - /// View spaces you've created or joined - internal static var screenSpaceAnnouncementItem1: String { return L10n.tr("Localizable", "screen_space_announcement_item1") } - /// Accept or decline invites to spaces - internal static var screenSpaceAnnouncementItem2: String { return L10n.tr("Localizable", "screen_space_announcement_item2") } - /// Discover any rooms you can join in your spaces - internal static var screenSpaceAnnouncementItem3: String { return L10n.tr("Localizable", "screen_space_announcement_item3") } - /// Join public spaces - internal static var screenSpaceAnnouncementItem4: String { return L10n.tr("Localizable", "screen_space_announcement_item4") } - /// Leave any spaces you’ve joined - internal static var screenSpaceAnnouncementItem5: String { return L10n.tr("Localizable", "screen_space_announcement_item5") } - /// Filtering, creating and managing spaces is coming soon. - internal static var screenSpaceAnnouncementNotice: String { return L10n.tr("Localizable", "screen_space_announcement_notice") } - /// Welcome to the beta version of Spaces! With this first version you can: - internal static var screenSpaceAnnouncementSubtitle: String { return L10n.tr("Localizable", "screen_space_announcement_subtitle") } - /// Introducing Spaces - internal static var screenSpaceAnnouncementTitle: String { return L10n.tr("Localizable", "screen_space_announcement_title") } /// Add your first room internal static var screenSpaceEmptyStateTitle: String { return L10n.tr("Localizable", "screen_space_empty_state_title") } /// Spaces you have created or joined. diff --git a/ElementX/Sources/Other/Extensions/CLLocationCoordinate2D.swift b/ElementX/Sources/Other/Extensions/CLLocationCoordinate2D.swift new file mode 100644 index 000000000..dedf02fce --- /dev/null +++ b/ElementX/Sources/Other/Extensions/CLLocationCoordinate2D.swift @@ -0,0 +1,14 @@ +// +// 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 + +extension CLLocationCoordinate2D: @retroactive Equatable { + public static func == (lhs: Self, rhs: Self) -> Bool { + lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude + } +} diff --git a/ElementX/Sources/Other/MapLibre/MapLibreMapView.swift b/ElementX/Sources/Other/MapLibre/MapLibreMapView.swift index 92a8b6f8b..980c41dc2 100644 --- a/ElementX/Sources/Other/MapLibre/MapLibreMapView.swift +++ b/ElementX/Sources/Other/MapLibre/MapLibreMapView.swift @@ -73,6 +73,13 @@ struct MapLibreMapView: UIViewRepresentable { mapView.styleURL = dynamicMapURL } + // If the center coordinate was updated externally (not by the map itself), move the map. + if let newCenter = mapCenterCoordinate, + newCenter != context.coordinator.lastReportedCenter { + context.coordinator.lastReportedCenter = newCenter + mapView.setCenter(newCenter, animated: true) + } + // Update existing annotation views with fresh SwiftUI content. // This handles the case where the annotation's view data changes after // the annotation was initially placed (e.g. user avatar loads asynchronously). @@ -169,6 +176,9 @@ extension MapLibreMapView { var mapLibreView: MapLibreMapView private var previousUserLocation: MLNUserLocation? + /// Tracks the last center coordinate reported by the map (or set programmatically), + /// so that `updateUIView` can tell apart external binding changes from internal ones. + var lastReportedCenter: CLLocationCoordinate2D? // MARK: - Setup @@ -220,8 +230,10 @@ extension MapLibreMapView { func mapView(_ mapView: MLNMapView, regionDidChangeAnimated animated: Bool) { // Avoid `Publishing changes from within view update` warnings - DispatchQueue.main.async { [mapLibreView] in - mapLibreView.mapCenterCoordinate = mapView.centerCoordinate + DispatchQueue.main.async { [mapLibreView, weak self] in + let center = mapView.centerCoordinate + self?.lastReportedCenter = center + mapLibreView.mapCenterCoordinate = center } } diff --git a/ElementX/Sources/Other/SwiftUI/Views/StopButton.swift b/ElementX/Sources/Other/SwiftUI/Views/StopButton.swift new file mode 100644 index 000000000..a987a9275 --- /dev/null +++ b/ElementX/Sources/Other/SwiftUI/Views/StopButton.swift @@ -0,0 +1,23 @@ +// +// 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 StopButton: View { + let stopAction: () -> Void + + var body: some View { + Button { stopAction() } label: { + CompoundIcon(\.stop, size: .small, relativeTo: .compound.bodySMSemibold) + .foregroundStyle(.compound.iconOnSolidPrimary) + .padding(8) + .background(Color.compound.bgCriticalPrimary, in: Circle()) + .accessibilityLabel(L10n.actionStop) + } + } +} diff --git a/ElementX/Sources/Other/TestablePreview/TestablePreviewsDictionary.swift b/ElementX/Sources/Other/TestablePreview/TestablePreviewsDictionary.swift index d375e5082..fbd3084b6 100644 --- a/ElementX/Sources/Other/TestablePreview/TestablePreviewsDictionary.swift +++ b/ElementX/Sources/Other/TestablePreview/TestablePreviewsDictionary.swift @@ -85,6 +85,7 @@ enum TestablePreviewsDictionary { "LinkNewDeviceScreen_Previews" : LinkNewDeviceScreen_Previews.self, "LiveLocationRoomTimelineView_Previews" : LiveLocationRoomTimelineView_Previews.self, "LiveLocationSharingBannerView_Previews" : LiveLocationSharingBannerView_Previews.self, + "LiveLocationSheet_Previews" : LiveLocationSheet_Previews.self, "LoadableImage_Previews" : LoadableImage_Previews.self, "LocationMarkerView_Previews" : LocationMarkerView_Previews.self, "LocationPickerSheet_Previews" : LocationPickerSheet_Previews.self, @@ -214,6 +215,7 @@ enum TestablePreviewsDictionary { "UserDetailsEditScreen_Previews" : UserDetailsEditScreen_Previews.self, "UserIndicatorModalView_Previews" : UserIndicatorModalView_Previews.self, "UserIndicatorToastView_Previews" : UserIndicatorToastView_Previews.self, + "UserLocationCell_Previews" : UserLocationCell_Previews.self, "UserProfileCell_Previews" : UserProfileCell_Previews.self, "UserProfileScreen_Previews" : UserProfileScreen_Previews.self, "VerificationBadge_Previews" : VerificationBadge_Previews.self, diff --git a/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenModels.swift b/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenModels.swift index 581bd0e2a..7a7aae650 100644 --- a/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenModels.swift +++ b/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenModels.swift @@ -65,6 +65,7 @@ struct LocationSharingScreenViewState: BindableState { let ownUserID: String var userProfiles: [String: UserProfileProxy] var liveLocationShares: [LiveLocationShare] = [] + var isStoppingLiveLocation = false var annotations: [LocationAnnotation] { switch interactionMode { @@ -80,6 +81,8 @@ struct LocationSharingScreenViewState: BindableState { case .viewLive: return liveLocationShares.compactMap { share in guard let geoURI = share.geoURI else { return nil } + if share.userID == ownUserID, isStoppingLiveLocation { return nil } + let profile = userProfiles[share.userID] ?? UserProfileProxy(userID: share.userID) let kind = LocationMarkerKind.liveUser(profile) let coordinate = CLLocationCoordinate2D(latitude: geoURI.latitude, longitude: geoURI.longitude) @@ -175,6 +178,7 @@ enum LocationSharingScreenViewAction { case startLiveLocation case centerToUser case userDidPan + case stopLiveLocation } extension AlertInfo where T == LocationSharingViewAlert { diff --git a/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenViewModel.swift b/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenViewModel.swift index 2d6742480..a5add685c 100644 --- a/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenViewModel.swift +++ b/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenViewModel.swift @@ -83,11 +83,21 @@ class LocationSharingScreenViewModel: LocationSharingScreenViewModelType, Locati primaryButton: .init(title: L10n.actionNotNow, role: .cancel, action: nil), secondaryButton: .init(title: L10n.commonSettings, action: action)) } + case .stopLiveLocation: + stopLiveLocation() } } // MARK: - Private + private func stopLiveLocation() { + state.isStoppingLiveLocation = true + if let index = state.liveLocationShares.firstIndex(where: { $0.userID == roomProxy.ownUserID }) { + state.liveLocationShares.remove(at: index) + } + Task { await liveLocationManager.stopLiveLocation(roomID: roomProxy.id) } + } + private func setupLiveLocationSubscription() async { let liveLocationService = await roomProxy.makeLiveLocationService() self.liveLocationService = liveLocationService @@ -96,7 +106,15 @@ class LocationSharingScreenViewModel: LocationSharingScreenViewModelType, Locati .sink { [weak self] liveLocationsShares in guard let self else { return } MXLog.info("Received live location shares update: \(liveLocationsShares.count) share(s)") + let ownUserID = roomProxy.ownUserID + let isStoppingLiveLocation = state.isStoppingLiveLocation state.liveLocationShares = liveLocationsShares + .filter { !(isStoppingLiveLocation && ownUserID == $0.userID) } + .sorted { lhs, rhs in + if lhs.userID == ownUserID { return true } + if rhs.userID == ownUserID { return false } + return lhs.timestamp > rhs.timestamp + } updateUserProfiles(members: roomProxy.membersPublisher.value) } .store(in: &cancellables) @@ -281,6 +299,8 @@ extension LocationSharingScreenViewModel { case picker case staticSenderLocation case staticPinLocation + case viewLive + case viewLiveEmpty } static func mock(type: MockType, @@ -301,12 +321,41 @@ extension LocationSharingScreenViewModel { longitude: 12.4963655), kind: .sender, timestamp: .mock)) + case .viewLive, .viewLiveEmpty: + .viewLive(sender: .init(id: senderID, displayName: "Me"), + initialLiveLocationShare: LiveLocationShare(userID: senderID, + geoURI: .init(latitude: 41.9027835, longitude: 12.4963655), + timestamp: .mock, + timeoutDate: .distantFuture)) } + let liveLocationShares: [LiveLocationShare] = if type == .viewLive { + [ + LiveLocationShare(userID: RoomMemberProxyMock.mockMe.userID, + geoURI: .init(latitude: 41.9027835, longitude: 12.4963655), + timestamp: .mock, + timeoutDate: .distantFuture), + LiveLocationShare(userID: RoomMemberProxyMock.mockAlice.userID, + geoURI: .init(latitude: 48.8566, longitude: 2.3522), + timestamp: .mock, + timeoutDate: .distantFuture), + LiveLocationShare(userID: RoomMemberProxyMock.mockBob.userID, + geoURI: .init(latitude: 51.5074, longitude: -0.1278), + timestamp: .mock, + timeoutDate: .distantFuture) + ] + } else { + [] + } + + let liveLocationServiceMock = RoomLiveLocationServiceMock(.init(shares: liveLocationShares)) + let roomProxy = JoinedRoomProxyMock(.init(members: .allMembers, ownUserID: RoomMemberProxyMock.mockMe.userID)) + roomProxy.makeLiveLocationServiceReturnValue = liveLocationServiceMock + return LocationSharingScreenViewModel(interactionMode: interactionMode, mapURLBuilder: ServiceLocator.shared.settings.mapTilerConfiguration, liveLocationSharingEnabled: liveLocationSharingEnabled, - roomProxy: JoinedRoomProxyMock(.init()), + roomProxy: roomProxy, timelineController: MockTimelineController(), liveLocationManager: LiveLocationManagerMock(), analytics: ServiceLocator.shared.analytics, diff --git a/ElementX/Sources/Screens/LocationSharing/View/LiveLocationSheet.swift b/ElementX/Sources/Screens/LocationSharing/View/LiveLocationSheet.swift new file mode 100644 index 000000000..05d6849ba --- /dev/null +++ b/ElementX/Sources/Screens/LocationSharing/View/LiveLocationSheet.swift @@ -0,0 +1,96 @@ +// +// 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") + } +} diff --git a/ElementX/Sources/Screens/LocationSharing/View/LocationSharingScreen.swift b/ElementX/Sources/Screens/LocationSharing/View/LocationSharingScreen.swift index a900949e1..72adbcfdf 100644 --- a/ElementX/Sources/Screens/LocationSharing/View/LocationSharingScreen.swift +++ b/ElementX/Sources/Screens/LocationSharing/View/LocationSharingScreen.swift @@ -25,12 +25,13 @@ struct LocationSharingScreen: View { .sheet(isPresented: .constant(true)) { StaticLocationSheet(context: context) .alert(item: $context.alertInfo) - .popover(item: $context.sharedAnnotation) { annotation in - LocationShareSheet(annotation: annotation) - } } case .viewLive: mainContent + .sheet(isPresented: .constant(true)) { + LiveLocationSheet(context: context) + .alert(item: $context.alertInfo) + } } } diff --git a/ElementX/Sources/Screens/LocationSharing/View/StaticLocationSheet.swift b/ElementX/Sources/Screens/LocationSharing/View/StaticLocationSheet.swift index a43952c46..9b4d83751 100644 --- a/ElementX/Sources/Screens/LocationSharing/View/StaticLocationSheet.swift +++ b/ElementX/Sources/Screens/LocationSharing/View/StaticLocationSheet.swift @@ -37,70 +37,26 @@ struct StaticLocationSheet: View { if case let .viewStatic(location) = context.viewState.interactionMode, let profile = context.viewState.userProfiles.values.first { Button { - context.sharedAnnotation = context.viewState.annotations.first + context.mapCenterLocation = .init(latitude: location.geoURI.latitude, + longitude: location.geoURI.longitude) } label: { UserLocationCell(profile: profile, isOwnUser: context.viewState.isOwnUser(profile.userID), - isUserLocation: location.kind == .sender, - timestamp: location.timestamp, - mediaProvider: context.mediaProvider) + kind: .static(isUserLocation: location.kind == .sender, + timestamp: location.timestamp), + mediaProvider: context.mediaProvider, + onShare: { + context.sharedAnnotation = context.viewState.annotations.first + }) + } + .popover(item: $context.sharedAnnotation) { annotation in + LocationShareSheet(annotation: annotation) } } } } } -/// 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) - .accessibilityLabel(L10n.actionShare) - } - .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) diff --git a/ElementX/Sources/Screens/LocationSharing/View/UserLocationCell.swift b/ElementX/Sources/Screens/LocationSharing/View/UserLocationCell.swift new file mode 100644 index 000000000..7d21615d9 --- /dev/null +++ b/ElementX/Sources/Screens/LocationSharing/View/UserLocationCell.swift @@ -0,0 +1,109 @@ +// +// 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 UserLocationCell: View { + let profile: UserProfileProxy + let isOwnUser: Bool + let kind: Kind + var mediaProvider: MediaProviderProtocol? + + var onShare: (() -> Void)? + var onStop: (() -> Void)? + + enum Kind { + case `static`(isUserLocation: Bool, timestamp: Date) + case live + } + + 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: 16) { + VStack(alignment: .leading, spacing: 0) { + Text(name) + .font(.compound.bodyLG) + .foregroundStyle(.compound.textPrimary) + HStack(spacing: 4) { + if case let .static(isUserLocation, timestamp) = kind { + 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) + } else { + CompoundIcon(\.locationPinSolid, + size: .xSmall, + relativeTo: .compound.bodyMD) + .foregroundStyle(.compound.iconAccentPrimary) + .accessibilityHidden(true) + Text(L10n.screenLiveLocationSheetSharingLiveLocation) + .font(.compound.bodyMD) + .foregroundStyle(.compound.textPrimary) + } + } + } + .accessibilityElement(children: .combine) + + Spacer() + if case .live = kind, isOwnUser { + StopButton { onStop?() } + } + Button { onShare?() } label: { + CompoundIcon(\.shareIos) + .foregroundStyle(.compound.iconPrimary) + .padding(5) + .overlay(RoundedRectangle(cornerRadius: 99) + .inset(by: -0.5) + .stroke(.compound.borderInteractiveSecondary, lineWidth: 1)) + .accessibilityLabel(L10n.actionShare) + } + } + .padding(.vertical, 12) + .rowDivider(alignment: .top) + } + .padding(.horizontal, 16) + } +} + +struct UserLocationCell_Previews: PreviewProvider, TestablePreview { + static var previews: some View { + UserLocationCell(profile: .mockDan, + isOwnUser: true, + kind: .static(isUserLocation: true, timestamp: .mock), + mediaProvider: MediaProviderMock(configuration: .init())) + .previewDisplayName("Stiatc user locaton") + .previewLayout(.sizeThatFits) + UserLocationCell(profile: .mockDan, + isOwnUser: false, + kind: .static(isUserLocation: false, timestamp: .mock), + mediaProvider: MediaProviderMock(configuration: .init())) + .previewDisplayName("Static pin location") + .previewLayout(.sizeThatFits) + UserLocationCell(profile: .mockDan, + isOwnUser: true, + kind: .live, + mediaProvider: MediaProviderMock(configuration: .init())) + .previewDisplayName("Live location") + .previewLayout(.sizeThatFits) + } +} diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift index eebaead6b..9d12fd536 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift @@ -83,6 +83,7 @@ struct DeveloperOptionsScreen: View { Toggle(isOn: $context.liveLocationSharingEnabled) { Text("Live location sharing") + Text("Requires app reboot") } Toggle(isOn: $context.knockingEnabled) { diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/LiveLocationRoomTimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/LiveLocationRoomTimelineView.swift index a65300ffa..f7c2705da 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/LiveLocationRoomTimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/LiveLocationRoomTimelineView.swift @@ -173,15 +173,7 @@ struct LiveLocationRoomTimelineView: View { Spacer() if isLive, timelineItem.isOutgoing { - Button { - stop() - } label: { - CompoundIcon(\.stop, size: .small, relativeTo: .compound.bodySMSemibold) - .foregroundStyle(.compound.iconOnSolidPrimary) - .padding(5) - .background(Color.compound.bgCriticalPrimary, in: Circle()) - .accessibilityLabel(L10n.actionStop) - } + StopButton { stop() } } } .padding(.horizontal, 8) diff --git a/ElementX/Sources/Services/Location/LiveLocationShareProxy.swift b/ElementX/Sources/Services/Location/LiveLocationShareProxy.swift index 1ca93e0ad..8add14a0e 100644 --- a/ElementX/Sources/Services/Location/LiveLocationShareProxy.swift +++ b/ElementX/Sources/Services/Location/LiveLocationShareProxy.swift @@ -8,12 +8,16 @@ import Foundation import MatrixRustSDK -struct LiveLocationShare: Hashable { +struct LiveLocationShare: Hashable, Identifiable { let userID: String let geoURI: GeoURI? let timestamp: Date let timeoutDate: Date + var id: String { + userID + } + init(userID: String, geoURI: GeoURI?, timestamp: Date, timeoutDate: Date) { self.userID = userID self.geoURI = geoURI diff --git a/PreviewTests/Sources/GeneratedPreviewTests.swift b/PreviewTests/Sources/GeneratedPreviewTests.swift index 6486b86ff..df5db5701 100644 --- a/PreviewTests/Sources/GeneratedPreviewTests.swift +++ b/PreviewTests/Sources/GeneratedPreviewTests.swift @@ -619,6 +619,14 @@ extension PreviewTests { } } + @Test + func liveLocationSheet() async throws { + AppSettings.resetAllSettings() // Ensure this test's previews start with fresh settings. + for (index, preview) in LiveLocationSheet_Previews._allPreviews.enumerated() { + try await assertSnapshots(matching: preview, step: index) + } + } + @Test func loadableImage() async throws { AppSettings.resetAllSettings() // Ensure this test's previews start with fresh settings. @@ -1651,6 +1659,14 @@ extension PreviewTests { } } + @Test + func userLocationCell() async throws { + AppSettings.resetAllSettings() // Ensure this test's previews start with fresh settings. + for (index, preview) in UserLocationCell_Previews._allPreviews.enumerated() { + try await assertSnapshots(matching: preview, step: index) + } + } + @Test func userProfileCell() async throws { AppSettings.resetAllSettings() // Ensure this test's previews start with fresh settings. diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationRoomTimelineView.Bubbles-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationRoomTimelineView.Bubbles-iPad-en-GB.png index 1bc7a868d..ed6fa00f6 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationRoomTimelineView.Bubbles-iPad-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationRoomTimelineView.Bubbles-iPad-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2988b20b96a1bfcd3e38d7bb29584768a6236b94a621b2344ab245b62dbe05b1 -size 2184708 +oid sha256:c277bb78c7d441b3e8115f167c2e7f7e00fcd78e540a066c8cc6c8b73867da27 +size 2179664 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationRoomTimelineView.Bubbles-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationRoomTimelineView.Bubbles-iPad-pseudo.png index e41a36ac9..77cffde5d 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationRoomTimelineView.Bubbles-iPad-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationRoomTimelineView.Bubbles-iPad-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:943e3454b93e8f729d82725e4d913612b0b9288145aa983d13c7cac51ad6e3d9 -size 2213162 +oid sha256:6c5e35e65f298b421969560c323285da0c0f6db636a15ab22b49317b6cc85f80 +size 2208377 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationRoomTimelineView.Bubbles-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationRoomTimelineView.Bubbles-iPhone-en-GB.png index 6104e2c86..4fed734d4 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationRoomTimelineView.Bubbles-iPhone-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationRoomTimelineView.Bubbles-iPhone-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bef678cf3b73d11746503a6e04b1915fd982bbd55a6938a1ec1d6ee8000a5533 -size 933876 +oid sha256:e539d337d962a7badb5c8bc48aff59ced0b3f36166b2cd11274c5708dee6ebac +size 930910 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationRoomTimelineView.Bubbles-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationRoomTimelineView.Bubbles-iPhone-pseudo.png index 8b83bda9b..bf6afcd80 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationRoomTimelineView.Bubbles-iPhone-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationRoomTimelineView.Bubbles-iPhone-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9fc0cc5993b63891cb7b7d29b2edb715d340dbaf8d9c16f76a4dab5b6a0b03ff -size 963921 +oid sha256:defab6e3ebc64df64085507a111a311690ad935bb1c68d305d7dfb8654f7803c +size 950951 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-location-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-location-iPad-en-GB.png new file mode 100644 index 000000000..d2fc19e56 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-location-iPad-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a7cd236a0471e7ee714a813803ce9e26c95c1d402edc44ff2a90a59b3c102760 +size 94159 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-location-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-location-iPad-pseudo.png new file mode 100644 index 000000000..6609c3340 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-location-iPad-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6111e09f7f33e9fb2c67053a7dfc7705f0c14e5df0cc03a07195ce2739f30ef6 +size 95805 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-location-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-location-iPhone-en-GB.png new file mode 100644 index 000000000..683d6ef96 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-location-iPhone-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9e06331704a391cd9c77831c7396e926009f4287234c85bcc3e0582105b6c6b +size 50516 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-location-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-location-iPhone-pseudo.png new file mode 100644 index 000000000..bc0c7b12c --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-location-iPhone-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2b3fe440bbf5533f296675a10baf249e6f96a9aca05ddcb92b32e73129f44c9a +size 57218 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-locations-are-empty-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-locations-are-empty-iPad-en-GB.png new file mode 100644 index 000000000..d2fc19e56 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-locations-are-empty-iPad-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a7cd236a0471e7ee714a813803ce9e26c95c1d402edc44ff2a90a59b3c102760 +size 94159 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-locations-are-empty-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-locations-are-empty-iPad-pseudo.png new file mode 100644 index 000000000..6609c3340 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-locations-are-empty-iPad-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6111e09f7f33e9fb2c67053a7dfc7705f0c14e5df0cc03a07195ce2739f30ef6 +size 95805 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-locations-are-empty-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-locations-are-empty-iPhone-en-GB.png new file mode 100644 index 000000000..683d6ef96 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-locations-are-empty-iPhone-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9e06331704a391cd9c77831c7396e926009f4287234c85bcc3e0582105b6c6b +size 50516 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-locations-are-empty-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-locations-are-empty-iPhone-pseudo.png new file mode 100644 index 000000000..bc0c7b12c --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/liveLocationSheet.Live-locations-are-empty-iPhone-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2b3fe440bbf5533f296675a10baf249e6f96a9aca05ddcb92b32e73129f44c9a +size 57218 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-own-location-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-own-location-iPad-en-GB.png index d43e22400..a0d68c864 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-own-location-iPad-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-own-location-iPad-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f720e8d495a071c41fa5e78d147d12cada18ff230a76c0fd5bdd279d9ad0d716 -size 91092 +oid sha256:e09291794c3c934176e4c095b3f6db22ffd47d3935bc28591ee701f2b7270bd3 +size 93238 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-own-location-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-own-location-iPad-pseudo.png index 721e1823b..fca06c60d 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-own-location-iPad-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-own-location-iPad-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f9fe046cb42faa4f7b26c7747f1866b793f0a4f5bec7be5fe9f746f6abdd52a1 -size 93417 +oid sha256:2a76afd12396dc6854c91ac8653ae5ec03c35111cff22f8b722edd082bed1ef7 +size 95532 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-own-location-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-own-location-iPhone-en-GB.png index d6d625544..cf067f3aa 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-own-location-iPhone-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-own-location-iPhone-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:74b0280e227fe297a8076eada6e95e541ed7188bf91e3d85397cc89a11f98284 -size 47996 +oid sha256:500b6466a24525797477ca81be1cc52d2559ef79b4e88e94461392e2922d0f27 +size 49347 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-own-location-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-own-location-iPhone-pseudo.png index 84c5055ba..72e05f32a 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-own-location-iPhone-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-own-location-iPhone-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0d93fa9aaf8bc5caf5ddfe06dd21ca1ed2c31929f3a8dbad01bdffbfdb7efac6 -size 51132 +oid sha256:506094fb7f842c515adf6974832cf17d0380787304c10293096ebddc2f810cfd +size 54091 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-pin-location-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-pin-location-iPad-en-GB.png index 170dfbf45..40bb8eef9 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-pin-location-iPad-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-pin-location-iPad-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dfb1f3557859cd8f83ed4a026ef26e7477e38020db4b05f7d0d3c20eb76eee9d -size 90873 +oid sha256:eb75487bf719e67e90d9902c2735b9e7465d0d5b9af4a42ebcdb985fb0f1872b +size 93207 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-pin-location-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-pin-location-iPad-pseudo.png index 5d9076cbc..cd1583bc2 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-pin-location-iPad-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-pin-location-iPad-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c706260c75f42ff1386abead7093fc6c9e7bda48d0eb98ba3f6ce5c4614f7cce -size 92218 +oid sha256:ec02cf01c4928591b10b88d7b3e3e1663f7dc6fbe9da2012914b9726ef174d3d +size 95247 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-pin-location-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-pin-location-iPhone-en-GB.png index ac1b5529f..600a3ce5c 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-pin-location-iPhone-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-pin-location-iPhone-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d06b463b821680210a17f85a060d2c3a42406af4a828d30a8bb73069c47969e9 -size 48009 +oid sha256:033e8147bbb8a68f1c5af220e52ef3b5f1d38c6dc38ec909f3ba6651a5bbc319 +size 49324 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-pin-location-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-pin-location-iPhone-pseudo.png index 437f27234..4ef6ac971 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-pin-location-iPhone-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationSheet.Static-pin-location-iPhone-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4facc31c68b7e7dbc8f1d3fb8fbd07ff2c9f630c8f35ab05f9bb1525666b5129 -size 50675 +oid sha256:9d0b1036aec8f1e889d66400c3a7af80fcddd53c79d130825c637d7141895b4a +size 53029 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Live-location-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Live-location-iPad-en-GB.png new file mode 100644 index 000000000..83682dd28 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Live-location-iPad-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:87340e7a07ad0b0519806217fe20d555c5ce9a81bd725a93fdde31885f32c794 +size 28919 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Live-location-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Live-location-iPad-pseudo.png new file mode 100644 index 000000000..02218ca77 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Live-location-iPad-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1597903358bd02adc5677fe861ec7396027efdd55ee06eba7a901e3980e5b52b +size 30286 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Live-location-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Live-location-iPhone-en-GB.png new file mode 100644 index 000000000..8ed40aafb --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Live-location-iPhone-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ff6b6914c41e8800b66e15c300163326ff50f757ad30a7e30a8836bee7f431f2 +size 24134 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Live-location-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Live-location-iPhone-pseudo.png new file mode 100644 index 000000000..cfa6fe2dd --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Live-location-iPhone-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:55c69caa22efdccb3788544087220f447bece068b1d5d245a403eaa4071356e4 +size 29704 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Static-pin-location-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Static-pin-location-iPad-en-GB.png new file mode 100644 index 000000000..0e5aace21 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Static-pin-location-iPad-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62ea5e41be32c7b9522ddd823792c2e423bcfc77dc4d36ec85b70be722f5e2cc +size 27861 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Static-pin-location-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Static-pin-location-iPad-pseudo.png new file mode 100644 index 000000000..44eb1f96e --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Static-pin-location-iPad-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b70e538266124a9e271b8cf7f10a72973e6557024b9d3f810dfb23fc882cf19 +size 29891 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Static-pin-location-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Static-pin-location-iPhone-en-GB.png new file mode 100644 index 000000000..35aac3a88 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Static-pin-location-iPhone-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7d2ffbcb019d03331c74b70fdc33dc3d86bdcf5c65e54e0eb724dd7cd0c8f15b +size 23019 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Static-pin-location-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Static-pin-location-iPhone-pseudo.png new file mode 100644 index 000000000..378c4c0b3 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Static-pin-location-iPhone-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:39d1cd53c2172f0292d844230876b90d59735664cbbe9e4600d5e120ca77b7ab +size 26988 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Stiatc-user-locaton-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Stiatc-user-locaton-iPad-en-GB.png new file mode 100644 index 000000000..d16af0b92 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Stiatc-user-locaton-iPad-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:214e4ab4f38636fa7ed00c24eae8e02adc5350e83a0d0c2ed9d5d992fa2b2a26 +size 27941 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Stiatc-user-locaton-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Stiatc-user-locaton-iPad-pseudo.png new file mode 100644 index 000000000..b8ff55b0a --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Stiatc-user-locaton-iPad-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6432d607d6a85c6c3a4ff8bd8684df22890e27dcd8a4da6e1b919195adabb2cc +size 30077 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Stiatc-user-locaton-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Stiatc-user-locaton-iPhone-en-GB.png new file mode 100644 index 000000000..6ad27a492 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Stiatc-user-locaton-iPhone-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5466e5f2fda6154f120b3f257f4ce95b50c677133777f19922e092b809b28233 +size 23091 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Stiatc-user-locaton-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Stiatc-user-locaton-iPhone-pseudo.png new file mode 100644 index 000000000..8b1bd9760 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/userLocationCell.Stiatc-user-locaton-iPhone-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d96f022a5a3587a3dc4e77f43de1b23470dfd830056bf384bd6a14d00f952f2e +size 27148