diff --git a/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift b/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift index 74e970092..07695d744 100644 --- a/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift +++ b/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift @@ -303,6 +303,10 @@ extension AccessibilityTests { try await performAccessibilityAudit(named: "LocationRoomTimelineView_Previews") } + func testLocationSharingScreen() async throws { + try await performAccessibilityAudit(named: "LocationSharingScreen_Previews") + } + func testLoginScreen() async throws { try await performAccessibilityAudit(named: "LoginScreen_Previews") } @@ -695,10 +699,6 @@ extension AccessibilityTests { try await performAccessibilityAudit(named: "StateRoomTimelineView_Previews") } - func testStaticLocationScreenViewer() async throws { - try await performAccessibilityAudit(named: "StaticLocationScreenViewer_Previews") - } - func testStickerRoomTimelineView() async throws { try await performAccessibilityAudit(named: "StickerRoomTimelineView_Previews") } diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 7e594a077..e4e153a28 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -46,6 +46,7 @@ 053B8BD2496207838878C6C9 /* PinnedItemsBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C9BAE9F9436B14E4E22E8F /* PinnedItemsBannerView.swift */; }; 059173B3C77056C406906B6D /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = D4DA544B2520BFA65D6DB4BB /* target.yml */; }; 059A6BEDE9BADF3C81AC4146 /* SpaceAddRoomsScreenSelectedItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77AB44FFCF7DA5300D943169 /* SpaceAddRoomsScreenSelectedItem.swift */; }; + 05B02B9508E922677F0E9451 /* LocationSharingScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 052A8C0B00FE8AD7C972F2A4 /* LocationSharingScreenViewModel.swift */; }; 05BAB510CBC2ED35C154ADD0 /* AnalyticsPromptScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFD012C3A9F5EF276DDD4AA /* AnalyticsPromptScreenViewModelProtocol.swift */; }; 05E797C4E0048BB487E5C4D6 /* AppLockTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8495F37D6245AD0CFA1F60B /* AppLockTests.swift */; }; 05FF0CD80EDAB3A7C0D4700A /* InfoPlistReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A580295A56B55A856CC4084 /* InfoPlistReader.swift */; }; @@ -168,7 +169,6 @@ 1A3B073568D1DC8F76F1F3A0 /* UserProfileScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23EE69982BBA18C6D51AD08E /* UserProfileScreen.swift */; }; 1A70A2199394B5EC660934A5 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = A678E40E917620059695F067 /* MatrixRustSDK */; }; 1A83DD22F3E6F76B13B6E2F9 /* VideoRoomTimelineItemContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C8616254EE40CA8BA5E9BC2 /* VideoRoomTimelineItemContent.swift */; }; - 1AB3D8563AB12635250A6A6E /* StaticLocationScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15E0017717EAE3A1D02D005 /* StaticLocationScreenCoordinator.swift */; }; 1AE4532D92E6ED42BFE87064 /* JoinRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = A566F8F2C27B99E1FB80C69B /* JoinRule.swift */; }; 1AE4AEA0FA8DEF52671832E0 /* RoomTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */; }; 1AF4A82B4332CAD2DB3EB5DA /* TopBannerModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78483F0143E185EDC6ECD741 /* TopBannerModifier.swift */; }; @@ -308,6 +308,7 @@ 352C439BE0F75E101EF11FB1 /* RoomScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2886615BEBAE33A0AA4D5F8 /* RoomScreenModels.swift */; }; 355B11D08CE0CEF97A813236 /* AppRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27A9E3FBE8A66B5A17AD7F74 /* AppRoutes.swift */; }; 3582056513A384F110EC8274 /* MediaPlayerProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D7A2C4A3A74F0D2FFE9356A /* MediaPlayerProviderTests.swift */; }; + 358290C2E4574456FE0C2DF6 /* LocationSharingScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B73675A21580971E9ADF66A /* LocationSharingScreenViewModelProtocol.swift */; }; 35E975CFDA60E05362A7CF79 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 1222DB76B917EB8A55365BA5 /* target.yml */; }; 36206F74DDEBF9BEAF6A6A1F /* ExtensionLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41A8571A8A071FB41778C016 /* ExtensionLogger.swift */; }; 3684AD01C5FCB7616B28F629 /* TimelineMediaPreviewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CDE60FEE95039CCCEEEE3B0 /* TimelineMediaPreviewController.swift */; }; @@ -521,7 +522,6 @@ 5B6E5AD224509E6C0B520D6E /* RoomMemberDetailsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DDF49CEBC0DFC59C308335F /* RoomMemberDetailsScreenViewModelProtocol.swift */; }; 5BC6C4ADFE7F2A795ECDE130 /* SecureBackupKeyBackupScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D4EEBE8C098BBADD10939 /* SecureBackupKeyBackupScreenCoordinator.swift */; }; 5C02841B2A86327B2C377682 /* NotificationConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C830A64609CBD152F06E0457 /* NotificationConstants.swift */; }; - 5C164551F7D26E24F09083D3 /* StaticLocationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C616D90B1E2F033CAA325439 /* StaticLocationScreenViewModelProtocol.swift */; }; 5C33976A720B64094CBC56B1 /* VideoMediaEventsTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B1FB56520A847DD2532D5C8 /* VideoMediaEventsTimelineView.swift */; }; 5C61810ED7B7CB48346B1B9D /* portrait_test_video.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = E5E7D4EE7CA295E5039FDA21 /* portrait_test_video.mp4 */; }; 5C8804B4F25903516E2DAB81 /* RoomInfoProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40A66E8BC8D9AE4A08EFB2DF /* RoomInfoProxy.swift */; }; @@ -702,6 +702,7 @@ 7A02EB29F3B993AB20E0A198 /* RoomPollsHistoryScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42C8C368A611B9CB79C7F5FA /* RoomPollsHistoryScreen.swift */; }; 7A0D335D38ECA095A575B4F7 /* TimelineStyler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DB0E533508094156D8024C3 /* TimelineStyler.swift */; }; 7A25D6926A2C01DB8D0D67A5 /* BadgeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A028783CFFF861C5E44FFB1 /* BadgeLabel.swift */; }; + 7A37EC9D7164319587539E1D /* LocationSharingScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA4F7FFF0704D1C5471BF6E /* LocationSharingScreenCoordinator.swift */; }; 7A495A5F3E5522DD7928CF8F /* UserIdentityProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 964093C7CA8823CAB7FFD88E /* UserIdentityProxy.swift */; }; 7A642EE5F1ADC5D520F21924 /* MediaProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85EB16E7FE59A947CA441531 /* MediaProviderProtocol.swift */; }; 7A71AEF419904209BB8C2833 /* UserAgentBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F2529D434C750ED78ADF1ED /* UserAgentBuilder.swift */; }; @@ -744,6 +745,7 @@ 80F6C8EFCA4564B67F0D34B0 /* DeactivateAccountScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D77F75B3E9F99864048A422A /* DeactivateAccountScreenViewModelTests.swift */; }; 81CFE6FE42DF26BBCEDC7FF2 /* JoinCallButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98ABC939BC8F08CA3E967D6C /* JoinCallButton.swift */; }; 81D4E550668B230A63B26CFB /* SpacesScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB98BFD8E93C7FCCEDEC46F9 /* SpacesScreenViewModel.swift */; }; + 82434593648CB74121F1A821 /* RowDivider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C513A6CD99E6C3C163DA1E /* RowDivider.swift */; }; 8285FF4B2C2331758C437FF7 /* ReportContentScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 713B48DBF65DE4B0DD445D66 /* ReportContentScreenViewModelProtocol.swift */; }; 828EA5009557C2B9DCD4CA0F /* UserDiscoverySection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */; }; 832A4EA1094B8FE423A08700 /* RoomChangeRolesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B2A421198FD20AAAED20004 /* RoomChangeRolesScreen.swift */; }; @@ -898,7 +900,6 @@ 9B3589276CA008E38FAAAE91 /* SpacesScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 358528B29FA72ACFD0D9644B /* SpacesScreenCoordinator.swift */; }; 9B84F55288AB98783C11CC49 /* SpaceRoomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0E7B059E84E7E374D3322A2 /* SpaceRoomCell.swift */; }; 9B872FF37DBE6BE054903831 /* MediaUploadPreviewScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D54E12B98252F6C527E31FEE /* MediaUploadPreviewScreenViewModelProtocol.swift */; }; - 9BB91CABB10D8FE90C491BCD /* StaticLocationScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C833673B334A0651AB46F30B /* StaticLocationScreenViewModelTests.swift */; }; 9C11138F7D8C291494BB0B20 /* Secrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3557ACB95D0F666EF5AF0CE /* Secrets.swift */; }; 9C4EC28A921486B1775D7F8C /* IdentityConfirmedScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307702DD66E7DDCDD9214784 /* IdentityConfirmedScreen.swift */; }; 9C63171267E22FEB288EC860 /* RoomHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1627F2D56477BD331F6D732C /* RoomHeaderView.swift */; }; @@ -1017,7 +1018,6 @@ AFE2AB612A1460E49578D746 /* JoinRoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BDCCD2F6B405C14B9BCE94E /* JoinRoomScreenCoordinator.swift */; }; B04E9EB589CE99C3929E817A /* HomeScreenRecoveryKeyConfirmationBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05512FB13987D221B7205DE0 /* HomeScreenRecoveryKeyConfirmationBanner.swift */; }; B0CB16349B96262AA65A04AF /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 997C7385E1A07E061D7E2100 /* GZIP */; }; - B1069F361E604D5436AE9FFD /* StaticLocationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B06663F7858E45882E63471 /* StaticLocationScreen.swift */; }; B1088417801A962BA861C13F /* RoomPowerLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7E247E29C7A0E421DB839D7 /* RoomPowerLevel.swift */; }; B10F7D5C237417DA160F4603 /* LongPressWithFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D9F148717D74F73BE724434 /* LongPressWithFeedback.swift */; }; B13774779EA19FDD7A35A4A8 /* RoomRolesAndPermissionsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C28B70BEFD3676F11D5D51F /* RoomRolesAndPermissionsScreenCoordinator.swift */; }; @@ -1133,6 +1133,7 @@ C5A07E2D88BE7D51DCECD166 /* LoginScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D0B159AFFBBD8ECFD0E37FA /* LoginScreenModels.swift */; }; C5E3A4A678B4F8900830B76A /* NavigationTabCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C39B1D3FC8CF41D6C3B054F /* NavigationTabCoordinator.swift */; }; C646CFE86CA46495D6BB7448 /* ManageAuthorizedSpacesScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74AE4F02A507882743973214 /* ManageAuthorizedSpacesScreenViewModelProtocol.swift */; }; + C67156445600FAE6430DE41E /* LocationSharingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F56E6E41C6DFE8054787D57 /* LocationSharingScreen.swift */; }; C67FCC854F3A6FC7A2EC04D0 /* MediaUploadPreviewScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70C86696AC9521F8ED88FBEB /* MediaUploadPreviewScreen.swift */; }; C6AC34B731F4F853E9D15146 /* AnalyticsConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA2397174D0DC3918A7A8A7B /* AnalyticsConfiguration.swift */; }; C6C06DDA8881260303FBA3A0 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2141693488CE5446BB391964 /* Date.swift */; }; @@ -1268,7 +1269,6 @@ DF40CDBEFE1D02B5F9C4ACB1 /* test_image.png in Resources */ = {isa = PBXBuildFile; fileRef = 810133CF215075C285FC6F3A /* test_image.png */; }; DF504B10A4918F971A57BEF2 /* PostHogAnalyticsClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1715E3D7F53C0748AA50C91C /* PostHogAnalyticsClient.swift */; }; DF8F1211F2B0B56F0FCCA5C2 /* CertificateValidatorHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3865AD7B7249C939D7C69C33 /* CertificateValidatorHook.swift */; }; - DFD5AA8688A34C72D48AF3B1 /* StaticLocationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5311C989EC15B4C2D699025 /* StaticLocationScreenViewModel.swift */; }; DFF7D6A6C26DDD40D00AE579 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = F012CB5EE3F2B67359F6CC52 /* target.yml */; }; E010DDE938032D3B8E84CC35 /* TracingHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A95C9B8299A36A6495DECA6 /* TracingHook.swift */; }; E02DAD9FD8D62587049FFFEC /* LinkNewDeviceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80F04B12FA231E797B7151A8 /* LinkNewDeviceTests.swift */; }; @@ -1411,6 +1411,7 @@ F66BCCC825D6CA51724A94D0 /* MediaPlayerProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8A1F98AE670377B20679FF5 /* MediaPlayerProvider.swift */; }; F6767F17D538578B370DD805 /* TimelineItemBubbleBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE739A6836E86E3780748477 /* TimelineItemBubbleBackground.swift */; }; F6BF52CB027393EE03CEC523 /* TimelineMediaPreviewViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1F000589F2CEE6B03ECFAB /* TimelineMediaPreviewViewModelTests.swift */; }; + F6D07D834279BE17D18A33E9 /* LocationSharingScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EED6D8956E554CEDFD4FE00D /* LocationSharingScreenViewModelTests.swift */; }; F6DFA23885980118AD7359C5 /* NotificationSettingsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2389732B0E115A999A069083 /* NotificationSettingsScreenCoordinator.swift */; }; F6F49E37272AD7397CD29A01 /* HomeScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */; }; F71C2B24AFB566119ACCDDA1 /* Secrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3557ACB95D0F666EF5AF0CE /* Secrets.swift */; }; @@ -1583,6 +1584,7 @@ 04BB8DDE245ED86C489BA983 /* AccessibilityIdentifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityIdentifiers.swift; sourceTree = ""; }; 04DF593C3F7AF4B2FBAEB05D /* FileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManager.swift; sourceTree = ""; }; 0516C69708D5CBDE1A8E77EC /* RoomDirectorySearchProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchProxyProtocol.swift; sourceTree = ""; }; + 052A8C0B00FE8AD7C972F2A4 /* LocationSharingScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationSharingScreenViewModel.swift; sourceTree = ""; }; 052B2F924572AFD70B5F500E /* StartChatScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatScreenViewModel.swift; sourceTree = ""; }; 0545AC444BEEA89FF8C509FD /* zh-Hant-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant-TW"; path = "zh-Hant-TW.lproj/InfoPlist.strings"; sourceTree = ""; }; 05512FB13987D221B7205DE0 /* HomeScreenRecoveryKeyConfirmationBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRecoveryKeyConfirmationBanner.swift; sourceTree = ""; }; @@ -1975,10 +1977,11 @@ 4A2B5274C1D3D2999D643786 /* EncryptionResetPasswordScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetPasswordScreenViewModelProtocol.swift; sourceTree = ""; }; 4A5B4CD611DE7E94F5BA87B2 /* AppLockTimerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockTimerTests.swift; sourceTree = ""; }; 4AB29A2D95D3469B5F016655 /* SecureBackupControllerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupControllerMock.swift; sourceTree = ""; }; - 4B1F71AC585827E6C416C15A /* AppIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = wrapper.icon; path = AppIcon.icon; sourceTree = ""; }; + 4B1F71AC585827E6C416C15A /* AppIcon.icon */ = {isa = PBXFileReference; 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 = ""; }; + 4B73675A21580971E9ADF66A /* LocationSharingScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationSharingScreenViewModelProtocol.swift; sourceTree = ""; }; 4BD371B60E07A5324B9507EF /* AnalyticsSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenCoordinator.swift; sourceTree = ""; }; 4C8D988E82A8DFA13BE46F7C /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = pl; path = pl.lproj/Localizable.stringsdict; sourceTree = ""; }; 4CD6AC7546E8D7E5C73CEA48 /* ElementX.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = ElementX.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1993,6 +1996,7 @@ 4F5F0662483ED69791D63B16 /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = et; path = et.lproj/Localizable.stringsdict; sourceTree = ""; }; 4F75EF13F49DD2204E760910 /* FileRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRoomTimelineView.swift; sourceTree = ""; }; 4FA29BAE9B0F2D90E57B261C /* UserSessionFlowCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionFlowCoordinatorTests.swift; sourceTree = ""; }; + 4FA4F7FFF0704D1C5471BF6E /* LocationSharingScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationSharingScreenCoordinator.swift; sourceTree = ""; }; 4FCB2126C091EEF2454B4D56 /* RoomFlowCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomFlowCoordinatorTests.swift; sourceTree = ""; }; 4FDD775CFD72DD2D3C8A8390 /* NotificationSettingsProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsProxyProtocol.swift; sourceTree = ""; }; 502F986D57158674172C58E3 /* AppLockSetupSettingsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupSettingsScreenModels.swift; sourceTree = ""; }; @@ -2144,6 +2148,7 @@ 6F054DE7D47849687662C9D9 /* BootDetectionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BootDetectionManager.swift; sourceTree = ""; }; 6F1C3CBBC62C566DDF5E84C1 /* TimelineItemMenuAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemMenuAction.swift; sourceTree = ""; }; 6F3DFE5B444F131648066F05 /* StateStoreViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateStoreViewModel.swift; sourceTree = ""; }; + 6F56E6E41C6DFE8054787D57 /* LocationSharingScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationSharingScreen.swift; sourceTree = ""; }; 6F65E4BB9E82EB8373207CF8 /* MediaProviderMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProviderMock.swift; sourceTree = ""; }; 6F6E6EDC4BBF962B2ED595A4 /* MessageForwardingScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenViewModelTests.swift; sourceTree = ""; }; 6FA38E813BE14149F173F461 /* PinnedEventsBannerStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedEventsBannerStateTests.swift; sourceTree = ""; }; @@ -2392,7 +2397,6 @@ 9A34D9BCA1A7D9A56E1EAF1D /* ManageRoomMemberSheetViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManageRoomMemberSheetViewModel.swift; sourceTree = ""; }; 9A68BCE6438873D2661D93D0 /* BugReportServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportServiceProtocol.swift; sourceTree = ""; }; 9AA3AF94A06D319BB37E52DA /* TimelineItemFactoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemFactoryTests.swift; sourceTree = ""; }; - 9B06663F7858E45882E63471 /* StaticLocationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLocationScreen.swift; sourceTree = ""; }; 9B6572E6EF5D5F4B0C338A40 /* PinnedEventsTimelineScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedEventsTimelineScreenModels.swift; sourceTree = ""; }; 9B663BE498BB39EADC24025D /* SettingsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenModels.swift; sourceTree = ""; }; 9B67DF223EEB8DCAF178A1D4 /* AnalyticsPromptScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreenCoordinator.swift; sourceTree = ""; }; @@ -2612,7 +2616,6 @@ C142248014E08E885E323E56 /* Avatars.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Avatars.swift; sourceTree = ""; }; C14D83B2B7CD5501A0089EFC /* LayoutDirection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutDirection.swift; sourceTree = ""; }; C1511766C534367700C8DD75 /* RoomNotificationModeProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationModeProxy.swift; sourceTree = ""; }; - C15E0017717EAE3A1D02D005 /* StaticLocationScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLocationScreenCoordinator.swift; sourceTree = ""; }; C1D737F4672021D0A7D218CD /* OIDCAccountSettingsPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OIDCAccountSettingsPresenter.swift; sourceTree = ""; }; C1FA515B3B0D61EF1E907D2D /* BadgeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeView.swift; sourceTree = ""; }; C258C9C815272911A5B132C3 /* FormattedBodyText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormattedBodyText.swift; sourceTree = ""; }; @@ -2635,7 +2638,6 @@ C57DB49B8426AA721BF85D83 /* SpaceServiceProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceServiceProxyProtocol.swift; sourceTree = ""; }; C5AEB5907E24092D741718AF /* ResolveVerifiedUserSendFailureScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResolveVerifiedUserSendFailureScreenCoordinator.swift; sourceTree = ""; }; C5F06F2F09B2EDD067DC2174 /* NotificationSettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsScreen.swift; sourceTree = ""; }; - C616D90B1E2F033CAA325439 /* StaticLocationScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLocationScreenViewModelProtocol.swift; sourceTree = ""; }; C687844F60BFF532D49A994C /* AnalyticsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsTests.swift; sourceTree = ""; }; C6A9F49B3EE59147AF2F70BB /* SeparatorRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorRoomTimelineItem.swift; sourceTree = ""; }; C705E605EF57C19DBE86FFA1 /* PlaceholderAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderAvatarImage.swift; sourceTree = ""; }; @@ -2645,7 +2647,6 @@ C75FE3F524B575D53787868C /* TimelineMediaPreviewRedactConfirmationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMediaPreviewRedactConfirmationView.swift; sourceTree = ""; }; C7661EFFCAA307A97D71132A /* HomeScreenRoomList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomList.swift; sourceTree = ""; }; C830A64609CBD152F06E0457 /* NotificationConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationConstants.swift; sourceTree = ""; }; - C833673B334A0651AB46F30B /* StaticLocationScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLocationScreenViewModelTests.swift; sourceTree = ""; }; C90514BE9B8ACCBCF0AD2489 /* ComposerToolbarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerToolbarViewModel.swift; sourceTree = ""; }; C95ADE8D9527523572532219 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = hu; path = hu.lproj/Localizable.stringsdict; sourceTree = ""; }; C97F8963B14EB0AF3940DDBF /* NotificationSettingsEditScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenRoomCell.swift; sourceTree = ""; }; @@ -2696,6 +2697,7 @@ D1D97BAF04AA150C0EF03021 /* VerificationBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationBadge.swift; sourceTree = ""; }; D263254AFE5B7993FFBBF324 /* NSE.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NSE.entitlements; sourceTree = ""; }; D28F7A6CEEA4A2815B0F0F55 /* SettingsFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsFlowCoordinator.swift; sourceTree = ""; }; + D2C513A6CD99E6C3C163DA1E /* RowDivider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RowDivider.swift; sourceTree = ""; }; D316BB02636AF2174F2580E6 /* SoftLogoutScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutScreenViewModelProtocol.swift; sourceTree = ""; }; D33116993D54FADC0C721C1F /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = ""; }; D3557ACB95D0F666EF5AF0CE /* Secrets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Secrets.swift; sourceTree = ""; }; @@ -2849,6 +2851,7 @@ EDDE826EAB1BAB80C1104980 /* SpaceFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceFlowCoordinator.swift; sourceTree = ""; }; EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItemContent.swift; sourceTree = ""; }; EEAB5662310AE73D93815134 /* JoinRoomScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinRoomScreenViewModelProtocol.swift; sourceTree = ""; }; + EED6D8956E554CEDFD4FE00D /* LocationSharingScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationSharingScreenViewModelTests.swift; sourceTree = ""; }; EF13BFD415CA84B1272E94F8 /* PINTextFieldTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PINTextFieldTests.swift; sourceTree = ""; }; EF1593DD87F974F8509BB619 /* ElementAnimations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementAnimations.swift; sourceTree = ""; }; EF36FC3DE25B20B7FA91F1FD /* SpaceServiceProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceServiceProxyMock.swift; sourceTree = ""; }; @@ -2891,7 +2894,6 @@ F4CEB4590CCF70F0E3C0B171 /* GeneratedAccessibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneratedAccessibilityTests.swift; sourceTree = ""; }; F506C6ADB1E1DA6638078E11 /* UITests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F51D674A5B5F1FE1B878E20F /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; - F5311C989EC15B4C2D699025 /* StaticLocationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLocationScreenViewModel.swift; sourceTree = ""; }; F57C8022B8A871A1DCD1750A /* UserIndicatorToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorToastView.swift; sourceTree = ""; }; F5D1BAA90F3A073D91B4F16B /* RoomNotificationSettingsProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsProxyMock.swift; sourceTree = ""; }; F5D8FEB1FED10E995CB002F7 /* TimelineBubbleLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineBubbleLayout.swift; sourceTree = ""; }; @@ -3118,6 +3120,7 @@ children = ( 76A46ABD27628CB5FC402541 /* Backports.swift */, F03AEAA2F66E796C365EFD58 /* ElementNavigationStack.swift */, + D2C513A6CD99E6C3C163DA1E /* RowDivider.swift */, 693E16574C6F7F9FA1015A8C /* Search.swift */, 832397B5C3D00A4BF52C5F0B /* ShouldScrollOnKeyboardDidShow.swift */, D1D97BAF04AA150C0EF03021 /* VerificationBadge.swift */, @@ -4777,6 +4780,7 @@ FDB9C37196A4C79F24CE80C6 /* KeychainControllerTests.swift */, C9AC2CC94FA06F728883B694 /* KnockRequestsListScreenViewModelTests.swift */, C070FD43DC6BF4E50217965A /* LocalizationTests.swift */, + EED6D8956E554CEDFD4FE00D /* LocationSharingScreenViewModelTests.swift */, 3DC1943ADE6A62ED5129D7C8 /* LoggingTests.swift */, 5A43964330459965AF048A8C /* LoginScreenViewModelTests.swift */, D80807B554CF9C524F98674F /* ManageRoomMemberSheetViewModelTests.swift */, @@ -4836,7 +4840,6 @@ 5557DDA438841AF5DC003D0B /* SpaceListScreenViewModelTests.swift */, 18B223FA339BF53085328DEE /* SpaceScreenViewModelTests.swift */, 6DF438EAFC732D2D95D34BF6 /* StartChatViewModelTests.swift */, - C833673B334A0651AB46F30B /* StaticLocationScreenViewModelTests.swift */, 2CEBCB9676FCD1D0F13188DD /* StringTests.swift */, 2AB2C848BB9A7A9B618B7B89 /* TextBasedRoomTimelineTests.swift */, 9AA3AF94A06D319BB37E52DA /* TimelineItemFactoryTests.swift */, @@ -5395,10 +5398,10 @@ 948DD12A5533BE1BC260E437 /* LocationSharing */ = { isa = PBXGroup; children = ( + 4FA4F7FFF0704D1C5471BF6E /* LocationSharingScreenCoordinator.swift */, 8BEBF0E59F25E842EDB6FD11 /* LocationSharingScreenModels.swift */, - C15E0017717EAE3A1D02D005 /* StaticLocationScreenCoordinator.swift */, - F5311C989EC15B4C2D699025 /* StaticLocationScreenViewModel.swift */, - C616D90B1E2F033CAA325439 /* StaticLocationScreenViewModelProtocol.swift */, + 052A8C0B00FE8AD7C972F2A4 /* LocationSharingScreenViewModel.swift */, + 4B73675A21580971E9ADF66A /* LocationSharingScreenViewModelProtocol.swift */, 9FD8D798D879069243A7E7F7 /* View */, ); path = LocationSharing; @@ -5555,7 +5558,7 @@ 9FD8D798D879069243A7E7F7 /* View */ = { isa = PBXGroup; children = ( - 9B06663F7858E45882E63471 /* StaticLocationScreen.swift */, + 6F56E6E41C6DFE8054787D57 /* LocationSharingScreen.swift */, ); path = View; sourceTree = ""; @@ -7158,6 +7161,7 @@ }; }; buildConfigurationList = 7AE41FCCF9D1352E2770D1F9 /* Build configuration list for PBXProject "ElementX" */; + compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -7233,7 +7237,6 @@ C89CF7729E028671C5DC461E /* XCLocalSwiftPackageReference "compound-ios" */, ); preferredProjectObjectVersion = 77; - productRefGroup = 681566846AF307E9BA4C72C6 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( @@ -7652,6 +7655,7 @@ BA48D6AFF6421D199148C0A1 /* KnockRequestsListScreenViewModelTests.swift in Sources */, CC961529F9F1854BEC3272C9 /* LayoutMocks.swift in Sources */, 0033481EE363E4914295F188 /* LocalizationTests.swift in Sources */, + F6D07D834279BE17D18A33E9 /* LocationSharingScreenViewModelTests.swift in Sources */, 149D1942DC005D0485FB8D93 /* LoggingTests.swift in Sources */, 7434A7F02D587A920B376A9A /* LoginScreenViewModelTests.swift in Sources */, 1C1750C009F7214B967928BC /* ManageRoomMemberSheetViewModelTests.swift in Sources */, @@ -7718,7 +7722,6 @@ 920DC020F18ABC88175114D3 /* SpaceListScreenViewModelTests.swift in Sources */, 72D2298DE695A6797CDA1A2A /* SpaceScreenViewModelTests.swift in Sources */, 6189B4ABD535CE526FA1107B /* StartChatViewModelTests.swift in Sources */, - 9BB91CABB10D8FE90C491BCD /* StaticLocationScreenViewModelTests.swift in Sources */, 1FEC0A4EC6E6DF693C16B32A /* StringTests.swift in Sources */, E75CE800B3E64D0F7F8E228D /* TemplateScreenViewModelTests.swift in Sources */, 3A7DD0D13B0FB8876D69D829 /* TextBasedRoomTimelineTests.swift in Sources */, @@ -8217,7 +8220,11 @@ D46C33F8B61B55F0C8C2D15F /* LocationRoomTimelineItem.swift in Sources */, 854E82E064BA53CD0BC45600 /* LocationRoomTimelineItemContent.swift in Sources */, 973C48F9E4EFB808F61BE401 /* LocationRoomTimelineView.swift in Sources */, + C67156445600FAE6430DE41E /* LocationSharingScreen.swift in Sources */, + 7A37EC9D7164319587539E1D /* LocationSharingScreenCoordinator.swift in Sources */, 29491EE7AE37E239E839C5A3 /* LocationSharingScreenModels.swift in Sources */, + 05B02B9508E922677F0E9451 /* LocationSharingScreenViewModel.swift in Sources */, + 358290C2E4574456FE0C2DF6 /* LocationSharingScreenViewModelProtocol.swift in Sources */, 5EDBDE802761B5ECB54E6787 /* LogLevel.swift in Sources */, 36DE961B784087D5E18EF9BA /* LogViewerScreen.swift in Sources */, CF38B70D8C6DD42C00A56A27 /* LogViewerScreenCoordinator.swift in Sources */, @@ -8546,6 +8553,7 @@ AD55E245FE686D7DB4C86406 /* RoomTimelineItemView.swift in Sources */, 41CE5E1289C8768FC5B6490C /* RoomTimelineItemViewState.swift in Sources */, B2F8E01ABA1BA30265B4ECBE /* RoundedCornerShape.swift in Sources */, + 82434593648CB74121F1A821 /* RowDivider.swift in Sources */, 3F55721B5C08E8D9F1295592 /* SDKListener.swift in Sources */, 88CBF1595E39CE697928DE48 /* SFNumberedListView.swift in Sources */, FB595EC9C00AB32F39034055 /* SceneDelegate.swift in Sources */, @@ -8670,10 +8678,6 @@ 3B277D9538090766DA6C4566 /* StateRoomTimelineView.swift in Sources */, B4AAB3257A83B73F53FB2689 /* StateStoreViewModel.swift in Sources */, CF638B8C6FDCE920AE061FAE /* StateStoreViewModelV2.swift in Sources */, - B1069F361E604D5436AE9FFD /* StaticLocationScreen.swift in Sources */, - 1AB3D8563AB12635250A6A6E /* StaticLocationScreenCoordinator.swift in Sources */, - DFD5AA8688A34C72D48AF3B1 /* StaticLocationScreenViewModel.swift in Sources */, - 5C164551F7D26E24F09083D3 /* StaticLocationScreenViewModelProtocol.swift in Sources */, C58E305C380D3ADDF7912180 /* StickerRoomTimelineItem.swift in Sources */, 197441F1EF23A5DABACCA79F /* StickerRoomTimelineView.swift in Sources */, 2F94054F50E312AF30BE07F3 /* String.swift in Sources */, diff --git a/ElementX/Resources/Localizations/en-US.lproj/Localizable.strings b/ElementX/Resources/Localizations/en-US.lproj/Localizable.strings index 93be35cad..fc1a3f601 100644 --- a/ElementX/Resources/Localizations/en-US.lproj/Localizable.strings +++ b/ElementX/Resources/Localizations/en-US.lproj/Localizable.strings @@ -36,6 +36,7 @@ "a11y_session_verification_time_limited_action_required" = "Time limited action required, you have one minute to verify"; "a11y_show_password" = "Show password"; "a11y_start_call" = "Start a call"; +"a11y_start_voice_call" = "Start a voice call"; "a11y_tombstoned_room" = "Tombstoned room"; "a11y_user_menu" = "User menu"; "a11y_view_avatar" = "View avatar"; @@ -262,6 +263,7 @@ "common_open_source_licenses" = "Open source licenses"; "common_optic_id_ios" = "Optic ID"; "common_or" = "or"; +"common_other_options" = "Other options"; "common_password" = "Password"; "common_people" = "People"; "common_permalink" = "Permalink"; @@ -396,7 +398,7 @@ "dialog_file_too_large_to_upload_title" = "The file size is too large to upload"; "dialog_permission_camera" = "In order to let the application use the camera, please grant the permission in the system settings."; "dialog_permission_generic" = "Please grant the permission in the system settings."; -"dialog_permission_location_description_ios" = "To share your current location, Element needs location access. Go to Settings > Location."; +"dialog_permission_location_description_ios" = "To share your current location, %1$@ needs location access. Go to Settings > Location."; "dialog_permission_location_title_ios" = "%1$@ does not have access to your location."; "dialog_permission_microphone" = "In order to let the application use the microphone, please grant the permission in the system settings."; "dialog_permission_microphone_description_ios" = "Grant access so you can record and send messages with audio."; @@ -458,6 +460,7 @@ "notification_channel_ringing_calls" = "Ringing calls"; "notification_channel_silent" = "Silent notifications"; "notification_fallback_content" = "You have new messages."; +"notification_incoming_audio_call" = "📞 Incoming call"; "notification_incoming_call" = "📹 Incoming call"; "notification_inline_reply_failed" = "** Failed to send - please open room"; "notification_invite_body" = "Invited you to chat"; @@ -665,6 +668,7 @@ "screen_media_upload_preview_item_count" = "Item %1$d of %2$d"; "screen_media_upload_preview_optimize_image_quality_title" = "Optimize image quality"; "screen_media_upload_preview_processing" = "Processing..."; +"screen_onboarding_welcome_back" = "Welcome back"; "screen_pinned_timeline_empty_state_description" = "Press on a message and choose “%1$@” to include here."; "screen_pinned_timeline_empty_state_headline" = "Pin important messages so that they can be easily discovered"; "screen_push_history_title" = "Push history"; @@ -1267,7 +1271,7 @@ "screen_share_open_apple_maps" = "Open in Apple Maps"; "screen_share_open_google_maps" = "Open in Google Maps"; "screen_share_open_osm_maps" = "Open in OpenStreetMap"; -"screen_share_this_location_action" = "Share this location"; +"screen_share_this_location_action" = "Share pinned location"; "screen_signed_out_reason_1" = "You’ve changed your password on another session"; "screen_signed_out_reason_2" = "You have deleted the session from another session"; "screen_signed_out_reason_3" = "Your server’s administrator has invalidated your access"; diff --git a/ElementX/Resources/Localizations/en.lproj/Localizable.strings b/ElementX/Resources/Localizations/en.lproj/Localizable.strings index abad6a718..8bc385c2c 100644 --- a/ElementX/Resources/Localizations/en.lproj/Localizable.strings +++ b/ElementX/Resources/Localizations/en.lproj/Localizable.strings @@ -36,6 +36,7 @@ "a11y_session_verification_time_limited_action_required" = "Time limited action required, you have one minute to verify"; "a11y_show_password" = "Show password"; "a11y_start_call" = "Start a call"; +"a11y_start_voice_call" = "Start a voice call"; "a11y_tombstoned_room" = "Tombstoned room"; "a11y_user_menu" = "User menu"; "a11y_view_avatar" = "View avatar"; @@ -262,6 +263,7 @@ "common_open_source_licenses" = "Open source licenses"; "common_optic_id_ios" = "Optic ID"; "common_or" = "or"; +"common_other_options" = "Other options"; "common_password" = "Password"; "common_people" = "People"; "common_permalink" = "Permalink"; @@ -396,7 +398,7 @@ "dialog_file_too_large_to_upload_title" = "The file size is too large to upload"; "dialog_permission_camera" = "In order to let the application use the camera, please grant the permission in the system settings."; "dialog_permission_generic" = "Please grant the permission in the system settings."; -"dialog_permission_location_description_ios" = "To share your current location, Element needs location access. Go to Settings > Location."; +"dialog_permission_location_description_ios" = "To share your current location, %1$@ needs location access. Go to Settings > Location."; "dialog_permission_location_title_ios" = "%1$@ does not have access to your location."; "dialog_permission_microphone" = "In order to let the application use the microphone, please grant the permission in the system settings."; "dialog_permission_microphone_description_ios" = "Grant access so you can record and send messages with audio."; @@ -458,6 +460,7 @@ "notification_channel_ringing_calls" = "Ringing calls"; "notification_channel_silent" = "Silent notifications"; "notification_fallback_content" = "You have new messages."; +"notification_incoming_audio_call" = "📞 Incoming call"; "notification_incoming_call" = "📹 Incoming call"; "notification_inline_reply_failed" = "** Failed to send - please open room"; "notification_invite_body" = "Invited you to chat"; @@ -665,6 +668,7 @@ "screen_media_upload_preview_item_count" = "Item %1$d of %2$d"; "screen_media_upload_preview_optimize_image_quality_title" = "Optimise image quality"; "screen_media_upload_preview_processing" = "Processing..."; +"screen_onboarding_welcome_back" = "Welcome back"; "screen_pinned_timeline_empty_state_description" = "Press on a message and choose “%1$@” to include here."; "screen_pinned_timeline_empty_state_headline" = "Pin important messages so that they can be easily discovered"; "screen_push_history_title" = "Push history"; @@ -1267,7 +1271,7 @@ "screen_share_open_apple_maps" = "Open in Apple Maps"; "screen_share_open_google_maps" = "Open in Google Maps"; "screen_share_open_osm_maps" = "Open in OpenStreetMap"; -"screen_share_this_location_action" = "Share this location"; +"screen_share_this_location_action" = "Share pinned location"; "screen_signed_out_reason_1" = "You’ve changed your password on another session"; "screen_signed_out_reason_2" = "You have deleted the session from another session"; "screen_signed_out_reason_3" = "Your server’s administrator has invalidated your access"; diff --git a/ElementX/Sources/Application/Settings/AppSettings.swift b/ElementX/Sources/Application/Settings/AppSettings.swift index 29912639e..42c8e1de2 100644 --- a/ElementX/Sources/Application/Settings/AppSettings.swift +++ b/ElementX/Sources/Application/Settings/AppSettings.swift @@ -64,6 +64,8 @@ final class AppSettings { case elementCallBaseURLOverride + case voiceMessagePlaybackSpeed + // Feature flags case publicSearchEnabled case fuzzyRoomListSearchEnabled @@ -76,9 +78,8 @@ final class AppSettings { case linkPreviewsEnabled case focusEventOnNotificationTap case linkNewDeviceEnabled + case liveLocationSharingEnabled - case voiceMessagePlaybackSpeed - // Doug's tweaks 🔧 case hideUnreadMessagesBadge case hideQuietNotificationAlerts @@ -432,6 +433,9 @@ final class AppSettings { @UserPreference(key: UserDefaultsKeys.linkNewDeviceEnabled, defaultValue: false, storageType: .userDefaults(store)) var linkNewDeviceEnabled + @UserPreference(key: UserDefaultsKeys.liveLocationSharingEnabled, defaultValue: false, storageType: .userDefaults(store)) + var liveLocationSharingEnabled + @UserPreference(key: UserDefaultsKeys.developerOptionsEnabled, defaultValue: appBuildType == .debug, storageType: .userDefaults(store)) var developerOptionsEnabled } diff --git a/ElementX/Sources/FlowCoordinators/PinnedEventsTimelineFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/PinnedEventsTimelineFlowCoordinator.swift index 2996631f6..01b8c9fe2 100644 --- a/ElementX/Sources/FlowCoordinators/PinnedEventsTimelineFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/PinnedEventsTimelineFlowCoordinator.swift @@ -85,8 +85,10 @@ class PinnedEventsTimelineFlowCoordinator: FlowCoordinatorProtocol { actionsSubject.send(.finished) case .displayUser(let userID): actionsSubject.send(.displayUser(userID: userID)) - case .presentLocationViewer(let geoURI, let description): - presentMapNavigator(geoURI: geoURI, description: description, timelineController: timelineController) + case .presentLocationViewer(let senderID, let geoURI): + presentMapNavigator(senderID: senderID, + geoURI: geoURI, + timelineController: timelineController) case .displayMessageForwarding(let forwardingItem): presentMessageForwarding(with: forwardingItem) case .displayRoomScreenWithFocussedPin(let eventID, let threadRootEventID): @@ -98,16 +100,22 @@ class PinnedEventsTimelineFlowCoordinator: FlowCoordinatorProtocol { navigationStackCoordinator.setRootCoordinator(coordinator) } - private func presentMapNavigator(geoURI: GeoURI, description: String?, timelineController: TimelineControllerProtocol) { + private func presentMapNavigator(senderID: String?, + geoURI: GeoURI, + timelineController: TimelineControllerProtocol) { let stackCoordinator = NavigationStackCoordinator() - let params = StaticLocationScreenCoordinatorParameters(interactionMode: .viewOnly(geoURI: geoURI, description: description), - mapURLBuilder: flowParameters.appSettings.mapTilerConfiguration, - timelineController: timelineController, - appMediator: flowParameters.appMediator, - analytics: flowParameters.analytics, - userIndicatorController: flowParameters.userIndicatorController) - let coordinator = StaticLocationScreenCoordinator(parameters: params) + let params = LocationSharingScreenCoordinatorParameters(interactionMode: .viewStatic(senderID: senderID, + geoURI: geoURI), + mapURLBuilder: flowParameters.appSettings.mapTilerConfiguration, + liveLocationSharingEnabled: flowParameters.appSettings.liveLocationSharingEnabled, + roomProxy: roomProxy, + timelineController: timelineController, + appMediator: flowParameters.appMediator, + analytics: flowParameters.analytics, + userIndicatorController: flowParameters.userIndicatorController, + mediaProvider: flowParameters.userSession.mediaProvider) + let coordinator = LocationSharingScreenCoordinator(parameters: params) coordinator.actions.sink { [weak self] action in guard let self else { return } diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index 8ce393b94..9f140160c 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -703,8 +703,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { case .presentPollForm(let mode): stateMachine.tryEvent(.presentPollForm(mode: mode), userInfo: EventUserInfo(animated: animated, timelineController: timelineController)) - case .presentLocationViewer(_, let geoURI, let description): - stateMachine.tryEvent(.presentMapNavigator(interactionMode: .viewOnly(geoURI: geoURI, description: description)), + case .presentLocationViewer(let senderID, let geoURI): + stateMachine.tryEvent(.presentMapNavigator(interactionMode: .viewStatic(senderID: senderID, geoURI: geoURI)), userInfo: EventUserInfo(animated: animated, timelineController: timelineController)) case .presentRoomMemberDetails(userID: let userID): stateMachine.tryEvent(.startMembersFlow(entryPoint: .roomMember(userID: userID))) @@ -788,10 +788,11 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { case .presentPollForm(let mode): stateMachine.tryEvent(.presentPollForm(mode: mode), userInfo: EventUserInfo(animated: animated, timelineController: timelineController)) - case .presentLocationViewer(_, let geoURI, let description): - stateMachine.tryEvent(.presentMapNavigator(interactionMode: .viewOnly(geoURI: geoURI, - description: description)), - userInfo: EventUserInfo(animated: animated, timelineController: timelineController)) + case .presentLocationViewer(let senderID, let geoURI): + stateMachine.tryEvent(.presentMapNavigator(interactionMode: .viewStatic(senderID: senderID, + geoURI: geoURI)), + userInfo: EventUserInfo(animated: animated, + timelineController: timelineController)) case .presentEmojiPicker(let itemID, let selectedEmojis): stateMachine.tryEvent(.presentEmojiPicker(itemID: itemID, selectedEmojis: selectedEmojis), userInfo: EventUserInfo(animated: animated, timelineController: timelineController)) @@ -1128,18 +1129,21 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { } } - private func presentMapNavigator(interactionMode: StaticLocationInteractionMode, + private func presentMapNavigator(interactionMode: LocationSharingInteractionMode, timelineController: TimelineControllerProtocol, animated: Bool) { let stackCoordinator = NavigationStackCoordinator() - let params = StaticLocationScreenCoordinatorParameters(interactionMode: interactionMode, - mapURLBuilder: flowParameters.appSettings.mapTilerConfiguration, - timelineController: timelineController, - appMediator: flowParameters.appMediator, - analytics: flowParameters.analytics, - userIndicatorController: flowParameters.userIndicatorController) - let coordinator = StaticLocationScreenCoordinator(parameters: params) + let params = LocationSharingScreenCoordinatorParameters(interactionMode: interactionMode, + mapURLBuilder: flowParameters.appSettings.mapTilerConfiguration, + liveLocationSharingEnabled: flowParameters.appSettings.liveLocationSharingEnabled, + roomProxy: roomProxy, + timelineController: timelineController, + appMediator: flowParameters.appMediator, + analytics: flowParameters.analytics, + userIndicatorController: flowParameters.userIndicatorController, + mediaProvider: flowParameters.userSession.mediaProvider) + let coordinator = LocationSharingScreenCoordinator(parameters: params) coordinator.actions.sink { [weak self] action in guard let self else { return } diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinatorStateMachine.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinatorStateMachine.swift index 1dab20b7f..2a03f1ae7 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinatorStateMachine.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinatorStateMachine.swift @@ -144,7 +144,7 @@ extension RoomFlowCoordinator { case presentEmojiPicker(itemID: TimelineItemIdentifier, selectedEmojis: Set) case dismissEmojiPicker - case presentMapNavigator(interactionMode: StaticLocationInteractionMode) + case presentMapNavigator(interactionMode: LocationSharingInteractionMode) case dismissMapNavigator case presentMessageForwarding(forwardingItem: MessageForwardingItem) diff --git a/ElementX/Sources/Generated/Strings.swift b/ElementX/Sources/Generated/Strings.swift index 728ed9aee..d003e3112 100644 --- a/ElementX/Sources/Generated/Strings.swift +++ b/ElementX/Sources/Generated/Strings.swift @@ -116,6 +116,8 @@ internal enum L10n { internal static var a11yShowPassword: String { return L10n.tr("Localizable", "a11y_show_password") } /// Start a call internal static var a11yStartCall: String { return L10n.tr("Localizable", "a11y_start_call") } + /// Start a voice call + internal static var a11yStartVoiceCall: String { return L10n.tr("Localizable", "a11y_start_voice_call") } /// Tombstoned room internal static var a11yTombstonedRoom: String { return L10n.tr("Localizable", "a11y_tombstoned_room") } /// User menu @@ -584,6 +586,8 @@ internal enum L10n { internal static var commonOpticIdIos: String { return L10n.tr("Localizable", "common_optic_id_ios") } /// or internal static var commonOr: String { return L10n.tr("Localizable", "common_or") } + /// Other options + internal static var commonOtherOptions: String { return L10n.tr("Localizable", "common_other_options") } /// Password internal static var commonPassword: String { return L10n.tr("Localizable", "common_password") } /// People @@ -908,8 +912,10 @@ internal enum L10n { internal static var dialogPermissionCamera: String { return L10n.tr("Localizable", "dialog_permission_camera") } /// Please grant the permission in the system settings. internal static var dialogPermissionGeneric: String { return L10n.tr("Localizable", "dialog_permission_generic") } - /// To share your current location, Element needs location access. Go to Settings > Location. - internal static var dialogPermissionLocationDescriptionIos: String { return L10n.tr("Localizable", "dialog_permission_location_description_ios") } + /// To share your current location, %1$@ needs location access. Go to Settings > Location. + internal static func dialogPermissionLocationDescriptionIos(_ p1: Any) -> String { + return L10n.tr("Localizable", "dialog_permission_location_description_ios", String(describing: p1)) + } /// %1$@ does not have access to your location. internal static func dialogPermissionLocationTitleIos(_ p1: Any) -> String { return L10n.tr("Localizable", "dialog_permission_location_title_ios", String(describing: p1)) @@ -1062,6 +1068,8 @@ internal enum L10n { internal static func notificationFallbackNContent(_ p1: Int) -> String { return L10n.tr("Localizable", "notification_fallback_n_content", p1) } + /// 📞 Incoming call + internal static var notificationIncomingAudioCall: String { return L10n.tr("Localizable", "notification_incoming_audio_call") } /// 📹 Incoming call internal static var notificationIncomingCall: String { return L10n.tr("Localizable", "notification_incoming_call") } /// ** Failed to send - please open room @@ -2238,6 +2246,8 @@ internal enum L10n { internal static var screenOnboardingSignInWithQrCode: String { return L10n.tr("Localizable", "screen_onboarding_sign_in_with_qr_code") } /// Create account internal static var screenOnboardingSignUp: String { return L10n.tr("Localizable", "screen_onboarding_sign_up") } + /// Welcome back + internal static var screenOnboardingWelcomeBack: String { return L10n.tr("Localizable", "screen_onboarding_welcome_back") } /// Welcome to the fastest %1$@ ever. Supercharged for speed and simplicity. internal static func screenOnboardingWelcomeMessage(_ p1: Any) -> String { return L10n.tr("Localizable", "screen_onboarding_welcome_message", String(describing: p1)) @@ -3157,7 +3167,7 @@ internal enum L10n { internal static var screenShareOpenGoogleMaps: String { return L10n.tr("Localizable", "screen_share_open_google_maps") } /// Open in OpenStreetMap internal static var screenShareOpenOsmMaps: String { return L10n.tr("Localizable", "screen_share_open_osm_maps") } - /// Share this location + /// Share pinned location internal static var screenShareThisLocationAction: String { return L10n.tr("Localizable", "screen_share_this_location_action") } /// You’ve changed your password on another session internal static var screenSignedOutReason1: String { return L10n.tr("Localizable", "screen_signed_out_reason_1") } diff --git a/ElementX/Sources/Other/Avatars.swift b/ElementX/Sources/Other/Avatars.swift index 69f687150..8f29188d3 100644 --- a/ElementX/Sources/Other/Avatars.swift +++ b/ElementX/Sources/Other/Avatars.swift @@ -84,55 +84,38 @@ enum UserAvatarSizeOnScreen { case sendInviteConfirmation case sessionVerification case threadSummary + case map var value: CGFloat { switch self { - case .chats, .spaces: - return 32 - case .timeline: - return 32 case .readReceipt: - return 16 - case .readReceiptSheet: - return 32 + 16 case .spaceHeader: - return 20 - case .completionSuggestions: - return 32 - case .blockedUsers: - return 32 - case .settings: - return 52 - case .roomDetails: - return 44 - case .roomMembersList: - return 32 - case .roomChangeRoles: - return 56 - case .startChat: - return 36 - case .memberDetails: - return 96 - case .inviteUsers: - return 52 - case .editUserDetails: - return 96 - case .dmDetails: - return 75 - case .knockingUsersBannerStack: - return 28 - case .knockingUserBanner: - return 32 - case .knockingUserList: - return 52 - case .mediaPreviewDetails: - return 32 - case .sendInviteConfirmation: - return 64 - case .sessionVerification: - return 52 + 20 case .threadSummary: - return 24 + 24 + case .knockingUsersBannerStack: + 28 + case .chats, .spaces, .map, + .timeline, .readReceiptSheet, .completionSuggestions, + .blockedUsers, .roomMembersList, .knockingUserBanner, + .mediaPreviewDetails: + 32 + case .startChat: + 36 + case .roomDetails: + 44 + case .inviteUsers, .knockingUserList, .sessionVerification, + .settings: + 52 + case .roomChangeRoles: + 56 + case .sendInviteConfirmation: + 64 + case .dmDetails: + 75 + case .memberDetails, .editUserDetails: + 96 } } } @@ -160,24 +143,22 @@ enum RoomAvatarSizeOnScreen { var value: CGFloat { switch self { - case .chats, .spaces, .spaceSettings: - return 52 + case .notificationSettings: + 30 case .timeline, .leaveSpace, .roomDirectorySearch, .completionSuggestions, .authorizedSpaces, .createRoomSelectSpace, .spaceFilters: - return 32 - case .notificationSettings: - return 30 - case .messageForwarding, .globalSearch, .roomSelection, .spaceAddRooms: - return 36 - case .spaceAddRoomsSelected: - return 52 + 32 + case .messageForwarding, .globalSearch, .roomSelection, + .spaceAddRooms: + 36 + case .chats, .spaces, .spaceSettings, + .spaceAddRoomsSelected: + 52 + case .joinRoom, .spaceHeader: + 64 case .details: - return 96 - case .joinRoom: - return 64 - case .spaceHeader: - return 64 + 96 } } } diff --git a/ElementX/Sources/Other/MapLibre/LocationAnnotation.swift b/ElementX/Sources/Other/MapLibre/LocationAnnotation.swift index 2e505eb55..30b573aa9 100644 --- a/ElementX/Sources/Other/MapLibre/LocationAnnotation.swift +++ b/ElementX/Sources/Other/MapLibre/LocationAnnotation.swift @@ -11,23 +11,32 @@ import MapLibre import SwiftUI final class LocationAnnotation: NSObject, MLNAnnotation { - let coordinate: CLLocationCoordinate2D + let id: String + var coordinate: CLLocationCoordinate2D let anchorPoint: CGPoint - let view: AnyView + var view: AnyView // MARK: - Setup - init(coordinate: CLLocationCoordinate2D, + init(id: String = UUID().uuidString, + coordinate: CLLocationCoordinate2D, anchorPoint: CGPoint = .init(x: 0.5, y: 0.5), @ViewBuilder label: () -> some View) { + self.id = id self.coordinate = coordinate self.anchorPoint = anchorPoint view = AnyView(label()) super.init() } + + func updateView(@ViewBuilder label: () -> some View) { + view = AnyView(label()) + } } final class LocationAnnotationView: MLNUserLocationAnnotationView { + private var hostingController: UIHostingController? + // MARK: - Setup override init(annotation: MLNAnnotation?, reuseIdentifier: String?) { @@ -37,12 +46,21 @@ final class LocationAnnotationView: MLNUserLocationAnnotationView { convenience init(annotation: LocationAnnotation) { self.init(annotation: annotation, reuseIdentifier: "\(Self.self)") - let view: UIView = UIHostingController(rootView: annotation.view).view + let hostingController = UIHostingController(rootView: annotation.view) + self.hostingController = hostingController + let view: UIView = hostingController.view view.backgroundColor = .clear view.anchorPoint = annotation.anchorPoint addSubview(view) view.bounds.size = view.intrinsicContentSize } + + func updateContent(with view: AnyView) { + hostingController?.rootView = view + if let hostedView = hostingController?.view { + hostedView.bounds.size = hostedView.intrinsicContentSize + } + } @available(*, unavailable) required init?(coder: NSCoder) { diff --git a/ElementX/Sources/Other/MapLibre/MapLibreMapView.swift b/ElementX/Sources/Other/MapLibre/MapLibreMapView.swift index 478598c05..f0f46801d 100644 --- a/ElementX/Sources/Other/MapLibre/MapLibreMapView.swift +++ b/ElementX/Sources/Other/MapLibre/MapLibreMapView.swift @@ -20,10 +20,10 @@ struct MapLibreMapView: UIViewRepresentable { /// The initial map center let mapCenter: CLLocationCoordinate2D - /// Map annotations - let annotations: [LocationAnnotation] + /// Map annotations keyed by a stable identifier (e.g. sender userID for user pins, UUID string for generic pins) + let annotations: [String: LocationAnnotation] - init(zoomLevel: Double, initialZoomLevel: Double, mapCenter: CLLocationCoordinate2D, annotations: [LocationAnnotation] = []) { + init(zoomLevel: Double, initialZoomLevel: Double, mapCenter: CLLocationCoordinate2D, annotations: [String: LocationAnnotation] = [:]) { self.zoomLevel = zoomLevel self.initialZoomLevel = initialZoomLevel self.mapCenter = mapCenter @@ -45,6 +45,7 @@ struct MapLibreMapView: UIViewRepresentable { @Binding var error: MapLibreError? /// Coordinate of the center of the map @Binding var mapCenterCoordinate: CLLocationCoordinate2D? + @Binding var hasLoadedUserLocation: Bool @Binding var isLocationAuthorized: Bool? /// The radius of uncertainty for the location, measured in meters. @Binding var geolocationUncertainty: CLLocationAccuracy? @@ -70,6 +71,11 @@ struct MapLibreMapView: UIViewRepresentable { mapView.styleURL = dynamicMapURL } + // 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). + updateAnnotations(in: mapView) + showUserLocation(in: mapView) } @@ -80,17 +86,52 @@ struct MapLibreMapView: UIViewRepresentable { // MARK: - Private private func setupMap(mapView: MLNMapView, with options: Options) { - mapView.addAnnotations(options.annotations) + mapView.addAnnotations(Array(options.annotations.values)) mapView.zoomLevel = options.annotations.isEmpty ? options.initialZoomLevel : options.zoomLevel mapView.centerCoordinate = options.mapCenter } + private func updateAnnotations(in mapView: MLNMapView) { + let existingByID = Dictionary(uniqueKeysWithValues: + (mapView.annotations ?? []).compactMap { $0 as? LocationAnnotation }.map { ($0.id, $0) }) + + let existingIDs = Set(existingByID.keys) + let updatedIDs = Set(options.annotations.keys) + + // Remove annotations that are no longer present + let removedIDs = existingIDs.subtracting(updatedIDs) + if !removedIDs.isEmpty { + let toRemove = removedIDs.compactMap { existingByID[$0] } + mapView.removeAnnotations(toRemove) + } + + // Add new annotations + let addedIDs = updatedIDs.subtracting(existingIDs) + if !addedIDs.isEmpty { + let toAdd = addedIDs.compactMap { options.annotations[$0] } + mapView.addAnnotations(toAdd) + } + + // Update existing annotations that are still present + let keptIDs = existingIDs.intersection(updatedIDs) + for id in keptIDs { + guard let existingAnnotation = existingByID[id], + let updatedAnnotation = options.annotations[id] else { + continue + } + existingAnnotation.coordinate = updatedAnnotation.coordinate + if let annotationView = mapView.view(for: existingAnnotation) as? LocationAnnotationView { + annotationView.updateContent(with: updatedAnnotation.view) + } + } + } + private func makeMapView() -> MLNMapView { let mapView = MLNMapView(frame: .zero, styleURL: mapURLBuilder.interactiveMapURL(for: colorScheme == .dark ? .dark : .light)) mapView.logoViewPosition = .topLeft mapView.attributionButtonPosition = .topLeft mapView.attributionButtonMargins = .init(x: mapView.logoView.frame.maxX + 8, y: mapView.logoView.center.y / 2) - mapView.tintColor = .black + mapView.tintColor = .compound.iconAccentPrimary mapView.allowsRotating = false mapView.allowsTilting = false return mapView @@ -149,7 +190,8 @@ extension MapLibreMapView { func mapView(_ mapView: MLNMapView, didUpdate userLocation: MLNUserLocation?) { guard let userLocation else { return } - + mapLibreView.hasLoadedUserLocation = true + if previousUserLocation == nil, mapLibreView.options.annotations.isEmpty { DispatchQueue.main.asyncAfter(deadline: .now() + 1) { mapView.setCenter(userLocation.coordinate, zoomLevel: self.mapLibreView.options.zoomLevel, animated: true) diff --git a/ElementX/Sources/Other/ShareToMapsAppActivity.swift b/ElementX/Sources/Other/ShareToMapsAppActivity.swift index c77fce7b1..31c7a290f 100644 --- a/ElementX/Sources/Other/ShareToMapsAppActivity.swift +++ b/ElementX/Sources/Other/ShareToMapsAppActivity.swift @@ -18,12 +18,12 @@ final class ShareToMapsAppActivity: UIActivity { private let type: MapsAppType private let location: CLLocationCoordinate2D - private let locationDescription: String? + private let senderName: String? - init(type: MapsAppType, location: CLLocationCoordinate2D, locationDescription: String?) { + init(type: MapsAppType, location: CLLocationCoordinate2D, senderName: String?) { self.type = type self.location = location - self.locationDescription = locationDescription + self.senderName = senderName super.init() } @@ -44,20 +44,20 @@ final class ShareToMapsAppActivity: UIActivity { } override func prepare(withActivityItems activityItems: [Any]) { - UIApplication.shared.open(type.activityURL(for: location, locationDescription: locationDescription), options: [:]) { [weak self] result in + UIApplication.shared.open(type.activityURL(for: location, senderName: senderName), options: [:]) { [weak self] result in self?.activityDidFinish(result) } } } extension ShareToMapsAppActivity.MapsAppType { - func activityURL(for location: CLLocationCoordinate2D, locationDescription: String?) -> URL { + func activityURL(for location: CLLocationCoordinate2D, senderName: String?) -> URL { switch self { case .apple: var url: URL = "https://maps.apple.com/" url.append(queryItems: [ .init(name: "ll", value: "\(location.latitude),\(location.longitude)"), - .init(name: "q", value: locationDescription ?? "Pin") + .init(name: "q", value: senderName ?? "Pin") // We need to provide a value or no marker is displayed. ]) return url case .google: diff --git a/ElementX/Sources/Other/SwiftUI/RowDivider.swift b/ElementX/Sources/Other/SwiftUI/RowDivider.swift new file mode 100644 index 000000000..c0677fa96 --- /dev/null +++ b/ElementX/Sources/Other/SwiftUI/RowDivider.swift @@ -0,0 +1,30 @@ +// +// 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 SwiftUI + +struct RowDivider: ViewModifier { + @Environment(\.pixelLength) var pixelLength: CGFloat + let alignment: Alignment + let horizontalInsets: CGFloat + + func body(content: Content) -> some View { + content + .overlay(alignment: alignment) { + Rectangle() + .fill(Color.compound.borderDisabled) + .frame(height: pixelLength) + .padding(.trailing, -horizontalInsets) + } + } +} + +extension View { + func rowDivider(alignment: Alignment = .bottom, horizontalInsets: CGFloat) -> some View { + modifier(RowDivider(alignment: alignment, horizontalInsets: horizontalInsets)) + } +} diff --git a/ElementX/Sources/Other/SwiftUI/Views/LocationMarkerView.swift b/ElementX/Sources/Other/SwiftUI/Views/LocationMarkerView.swift index 78396393a..e623d7b11 100644 --- a/ElementX/Sources/Other/SwiftUI/Views/LocationMarkerView.swift +++ b/ElementX/Sources/Other/SwiftUI/Views/LocationMarkerView.swift @@ -10,28 +10,84 @@ import Compound import SwiftUI struct LocationMarkerView: View { - private let pinColor: Color = .compound.iconOnSolidPrimary - private let pinInsets = EdgeInsets(top: 13, leading: 12, bottom: 15, trailing: 12) + var userProfile: UserProfileProxy? + var fillColor = Color.compound.bgCanvasDefault + var strokeColor = Color.compound.iconSecondaryAlpha + var dotColor = Color.compound.iconPrimary + @ScaledMetric var size: CGFloat = 42 + var mediaProvider: MediaProviderProtocol? + + private let circleCenter = CGPoint(x: 21, y: 21) // in SVG space + private let circleRadius: CGFloat = 6 // in SVG space var body: some View { - CompoundIcon(\.locationPinSolid) - .dynamicTypeSize(.large) - .foregroundStyle(pinColor) - .padding(pinInsets) - .background { - backgroundShape - .shadow(color: .black.opacity(0.2), radius: 4.1129, x: 0, y: 4.93548) + // Generated from the SVG + Canvas { context, canvasSize in + let scaleX = canvasSize.width / 42 + let scaleY = canvasSize.height / 50 + + let pinPath = Path { p in + p.move(to: CGPoint(x: 21 * scaleX, y: 49 * scaleY)) + p.addCurve(to: CGPoint(x: 17.6875 * scaleX, y: 47.7581 * scaleY), + control1: CGPoint(x: 19.25 * scaleX, y: 49 * scaleY), + control2: CGPoint(x: 18.1458 * scaleX, y: 48.4825 * scaleY)) + p.addCurve(to: CGPoint(x: 5.28125 * scaleX, y: 33.6313 * scaleY), + control1: CGPoint(x: 12.5833 * scaleX, y: 42.8525 * scaleY), + control2: CGPoint(x: 8.41667 * scaleX, y: 38.1332 * scaleY)) + p.addCurve(to: CGPoint(x: 1 * scaleX, y: 21.3674 * scaleY), + control1: CGPoint(x: 2.82292 * scaleX, y: 29.2846 * scaleY), + control2: CGPoint(x: 1 * scaleX, y: 25.1863 * scaleY)) + p.addCurve(to: CGPoint(x: 21 * scaleX, y: 1 * scaleY), + control1: CGPoint(x: 1 * scaleX, y: 10.2109 * scaleY), + control2: CGPoint(x: 9.5 * scaleX, y: 1 * scaleY)) + p.addCurve(to: CGPoint(x: 41 * scaleX, y: 21.3674 * scaleY), + control1: CGPoint(x: 32.5 * scaleX, y: 1 * scaleY), + control2: CGPoint(x: 41 * scaleX, y: 10.2109 * scaleY)) + p.addCurve(to: CGPoint(x: 36.7188 * scaleX, y: 33.6313 * scaleY), + control1: CGPoint(x: 41 * scaleX, y: 25.1863 * scaleY), + control2: CGPoint(x: 39.1771 * scaleX, y: 29.2846 * scaleY)) + p.addCurve(to: CGPoint(x: 24.3125 * scaleX, y: 47.7581 * scaleY), + control1: CGPoint(x: 33.5833 * scaleX, y: 38.1332 * scaleY), + control2: CGPoint(x: 29.4167 * scaleX, y: 42.8525 * scaleY)) + p.addCurve(to: CGPoint(x: 21 * scaleX, y: 49 * scaleY), + control1: CGPoint(x: 23.3333 * scaleX, y: 48.4825 * scaleY), + control2: CGPoint(x: 21.5833 * scaleX, y: 49 * scaleY)) + p.closeSubpath() } - .alignmentGuide(VerticalAlignment.center) { dimensions in - dimensions[.bottom] + + context.stroke(pinPath, with: .color(strokeColor), lineWidth: 2 * scaleX) + context.fill(pinPath, with: .color(fillColor)) + + // Dot + let dotPath = Path(ellipseIn: CGRect(x: (circleCenter.x - circleRadius) * scaleX, + y: (circleCenter.y - circleRadius) * scaleY, + width: circleRadius * 2 * scaleX, + height: circleRadius * 2 * scaleY)) + context.fill(dotPath, with: .color(dotColor)) + + // Draw resolved symbol centered on the circle + if userProfile != nil, let symbol = context.resolveSymbol(id: 0) { + let center = CGPoint(x: circleCenter.x * scaleX, + y: circleCenter.y * scaleY) + context.draw(symbol, at: center, anchor: .center) } - } - - var backgroundShape: some View { - Image(asset: Asset.Images.locationMarkerShape) - .resizable() - .aspectRatio(contentMode: .fill) - .foregroundStyle(.compound.iconPrimary) + } symbols: { + if let userProfile { + LoadableAvatarImage(url: userProfile.avatarURL, + name: userProfile.displayName, + contentID: userProfile.userID, + avatarSize: .user(on: .map), + mediaProvider: mediaProvider) + .overlay { + Circle().inset(by: 0.5).stroke(strokeColor) + } + .tag(0) + } + } + .frame(width: size, height: size * 50 / 42) + .alignmentGuide(VerticalAlignment.center) { dimensions in + dimensions[.bottom] + } } } @@ -39,9 +95,19 @@ struct LocationMarkerView_Previews: PreviewProvider, TestablePreview { static var previews: some View { VStack(spacing: 30) { LocationMarkerView() - + LocationMarkerView() .colorScheme(.dark) + + LocationMarkerView(userProfile: UserProfileProxy.mockDan, + mediaProvider: MediaProviderMock(configuration: .init())) + + LocationMarkerView(userProfile: UserProfileProxy.mockDan, + mediaProvider: MediaProviderMock(configuration: .init())) + .colorScheme(.dark) } + .padding(16) + .background(Color(red: 0.9, green: 0.85, blue: 0.8)) + .previewLayout(.sizeThatFits) } } diff --git a/ElementX/Sources/Other/SwiftUI/Views/ToolbarButton.swift b/ElementX/Sources/Other/SwiftUI/Views/ToolbarButton.swift index f8b4c4bb7..04f34be93 100644 --- a/ElementX/Sources/Other/SwiftUI/Views/ToolbarButton.swift +++ b/ElementX/Sources/Other/SwiftUI/Views/ToolbarButton.swift @@ -14,6 +14,7 @@ struct ToolbarButton: View { static let cancel = Role.cancel(title: L10n.actionCancel) static let done = Role.confirm(title: L10n.actionDone) static let save = Role.confirm(title: L10n.actionSave) + static let close = Role.cancel(title: L10n.actionClose) case cancel(title: String) case confirm(title: String) diff --git a/ElementX/Sources/Other/TestablePreview/TestablePreviewsDictionary.swift b/ElementX/Sources/Other/TestablePreview/TestablePreviewsDictionary.swift index 7eb70a086..016ef0646 100644 --- a/ElementX/Sources/Other/TestablePreview/TestablePreviewsDictionary.swift +++ b/ElementX/Sources/Other/TestablePreview/TestablePreviewsDictionary.swift @@ -83,6 +83,7 @@ enum TestablePreviewsDictionary { "LoadableImage_Previews" : LoadableImage_Previews.self, "LocationMarkerView_Previews" : LocationMarkerView_Previews.self, "LocationRoomTimelineView_Previews" : LocationRoomTimelineView_Previews.self, + "LocationSharingScreen_Previews" : LocationSharingScreen_Previews.self, "LoginScreen_Previews" : LoginScreen_Previews.self, "LongPressWithFeedback_Previews" : LongPressWithFeedback_Previews.self, "ManageAuthorizedSpacesScreen_Previews" : ManageAuthorizedSpacesScreen_Previews.self, @@ -181,7 +182,6 @@ enum TestablePreviewsDictionary { "StackedAvatarsView_Previews" : StackedAvatarsView_Previews.self, "StartChatScreen_Previews" : StartChatScreen_Previews.self, "StateRoomTimelineView_Previews" : StateRoomTimelineView_Previews.self, - "StaticLocationScreenViewer_Previews" : StaticLocationScreenViewer_Previews.self, "StickerRoomTimelineView_Previews" : StickerRoomTimelineView_Previews.self, "SwipeRightAction_Previews" : SwipeRightAction_Previews.self, "SwipeToReplyView_Previews" : SwipeToReplyView_Previews.self, diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift index b8abbad69..9292190cd 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift @@ -33,12 +33,7 @@ struct HomeScreenRoomCell: View { content .padding(.vertical, verticalInsets) - .overlay(alignment: .bottom) { - Rectangle() - .fill(Color.compound.borderDisabled) - .frame(height: 1 / UIScreen.main.scale) - .padding(.trailing, -horizontalInsets) - } + .rowDivider(horizontalInsets: horizontalInsets) } .padding(.horizontal, horizontalInsets) .accessibilityElement(children: .combine) diff --git a/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenCoordinator.swift b/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenCoordinator.swift new file mode 100644 index 000000000..6c4e0828d --- /dev/null +++ b/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenCoordinator.swift @@ -0,0 +1,70 @@ +// +// Copyright 2025 Element Creations Ltd. +// Copyright 2023-2025 New Vector Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. +// Please see LICENSE files in the repository root for full details. +// + +import Combine +import SwiftUI + +struct LocationSharingScreenCoordinatorParameters { + let interactionMode: LocationSharingInteractionMode + let mapURLBuilder: MapTilerURLBuilderProtocol + let liveLocationSharingEnabled: Bool + let roomProxy: JoinedRoomProxyProtocol + let timelineController: TimelineControllerProtocol + let appMediator: AppMediatorProtocol + let analytics: AnalyticsService + let userIndicatorController: UserIndicatorControllerProtocol + let mediaProvider: MediaProviderProtocol +} + +enum LocationSharingScreenCoordinatorAction { + case close +} + +final class LocationSharingScreenCoordinator: CoordinatorProtocol { + private let parameters: LocationSharingScreenCoordinatorParameters + private let viewModel: LocationSharingScreenViewModelProtocol + + private let actionsSubject: PassthroughSubject = .init() + private var cancellables = Set() + + var actions: AnyPublisher { + actionsSubject.eraseToAnyPublisher() + } + + init(parameters: LocationSharingScreenCoordinatorParameters) { + self.parameters = parameters + + viewModel = LocationSharingScreenViewModel(interactionMode: parameters.interactionMode, + mapURLBuilder: parameters.mapURLBuilder, + liveLocationSharingEnabled: parameters.liveLocationSharingEnabled, + roomProxy: parameters.roomProxy, + timelineController: parameters.timelineController, + analytics: parameters.analytics, + userIndicatorController: parameters.userIndicatorController, + mediaProvider: parameters.mediaProvider) + } + + // MARK: - Public + + func start() { + viewModel.actions.sink { [weak self] action in + guard let self else { return } + switch action { + case .close: + actionsSubject.send(.close) + case .openSystemSettings: + parameters.appMediator.openAppSettings() + } + } + .store(in: &cancellables) + } + + func toPresentable() -> AnyView { + AnyView(LocationSharingScreen(context: viewModel.context)) + } +} diff --git a/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenModels.swift b/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenModels.swift index 238c37d4c..d9d6f0ec8 100644 --- a/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenModels.swift +++ b/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenModels.swift @@ -14,31 +14,44 @@ enum LocationSharingViewError: Error, Hashable { case mapError(MapLibreError) } -enum StaticLocationScreenViewModelAction { +enum LocationSharingScreenViewModelAction { case close case openSystemSettings } -enum StaticLocationInteractionMode: Hashable { +enum LocationSharingInteractionMode: Hashable { case picker - case viewOnly(geoURI: GeoURI, description: String? = nil) + case viewStatic(senderID: String?, geoURI: GeoURI) + + var canShowAvatar: Bool { + switch self { + case .picker, .viewStatic(.some(_), _): + true + default: + false + } + } } -struct StaticLocationScreenViewState: BindableState { - init(interactionMode: StaticLocationInteractionMode, mapURLBuilder: MapTilerURLBuilderProtocol) { +struct LocationSharingScreenViewState: BindableState { + init(interactionMode: LocationSharingInteractionMode, + mapURLBuilder: MapTilerURLBuilderProtocol, + showLiveLocationSharingButton: Bool) { self.interactionMode = interactionMode self.mapURLBuilder = mapURLBuilder + self.showLiveLocationSharingButton = showLiveLocationSharingButton bindings.showsUserLocationMode = switch interactionMode { case .picker: .showAndFollow - case .viewOnly: .show + case .viewStatic: .show } } - let interactionMode: StaticLocationInteractionMode + let interactionMode: LocationSharingInteractionMode let mapURLBuilder: MapTilerURLBuilderProtocol + let showLiveLocationSharingButton: Bool - var bindings = StaticLocationScreenBindings(showsUserLocationMode: .hide) + var bindings = LocationSharingScreenBindings(showsUserLocationMode: .hide) /// Indicates whether the user is sharing his current location var isSharingUserLocation: Bool { @@ -50,21 +63,17 @@ struct StaticLocationScreenViewState: BindableState { case .picker: // middle point in Europe, to be used if the users location is not yet known return .init(latitude: 49.843, longitude: 9.902056) - case .viewOnly(let geoURI, _): + case .viewStatic(_, let geoURI): return .init(latitude: geoURI.latitude, longitude: geoURI.longitude) } } var isLocationPickerMode: Bool { - interactionMode == .picker - } - - var navigationTitle: String { switch interactionMode { case .picker: - return L10n.screenShareLocationTitle - case .viewOnly: - return L10n.screenViewLocationTitle + true + default: + false } } @@ -72,10 +81,14 @@ struct StaticLocationScreenViewState: BindableState { switch interactionMode { case .picker: return false - case .viewOnly: + case .viewStatic: return true } } + + var isLocationLoading: Bool { + !bindings.hasLoadedUserLocation && bindings.isLocationAuthorized != false + } var zoomLevel: Double { 15.0 @@ -85,27 +98,28 @@ struct StaticLocationScreenViewState: BindableState { switch interactionMode { case .picker: return 2.7 - case .viewOnly: + case .viewStatic: return 15.0 } } - - var locationDescription: String? { + + var userProfile: UserProfileProxy? + + var shownUserProfile: UserProfileProxy? { switch interactionMode { case .picker: - return nil - case .viewOnly(_, let description): - return description + isSharingUserLocation ? userProfile : nil + case .viewStatic: + userProfile } } } -struct StaticLocationScreenBindings { +struct LocationSharingScreenBindings { var mapCenterLocation: CLLocationCoordinate2D? var geolocationUncertainty: CLLocationAccuracy? - var showsUserLocationMode: ShowUserLocationMode - + var hasLoadedUserLocation = false var isLocationAuthorized: Bool? /// Information describing the currently displayed alert. @@ -127,7 +141,7 @@ struct StaticLocationScreenBindings { var showShareSheet = false } -enum StaticLocationScreenViewAction { +enum LocationSharingScreenViewAction { case close case selectLocation case centerToUser @@ -141,20 +155,18 @@ extension AlertInfo where T == LocationSharingViewError { switch error { case .missingAuthorization: self.init(id: error, - title: L10n.dialogPermissionLocationTitleIos(InfoPlistReader.main.bundleDisplayName), - message: L10n.dialogPermissionLocationDescriptionIos, + title: L10n.dialogAllowAccess, + message: L10n.dialogPermissionLocationDescriptionIos(InfoPlistReader.main.bundleDisplayName), primaryButton: primaryButton, secondaryButton: secondaryButton) case .mapError(.failedLoadingMap): self.init(id: error, - title: "", - message: L10n.errorFailedLoadingMap(InfoPlistReader.main.bundleDisplayName), + title: L10n.errorFailedLoadingMap(InfoPlistReader.main.bundleDisplayName), primaryButton: primaryButton, secondaryButton: secondaryButton) case .mapError(.failedLocatingUser): self.init(id: error, - title: "", - message: L10n.errorFailedLocatingUser(InfoPlistReader.main.bundleDisplayName), + title: L10n.errorFailedLocatingUser(InfoPlistReader.main.bundleDisplayName), primaryButton: primaryButton, secondaryButton: secondaryButton) } diff --git a/ElementX/Sources/Screens/LocationSharing/StaticLocationScreenViewModel.swift b/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenViewModel.swift similarity index 62% rename from ElementX/Sources/Screens/LocationSharing/StaticLocationScreenViewModel.swift rename to ElementX/Sources/Screens/LocationSharing/LocationSharingScreenViewModel.swift index 3a71da4ae..4add6589d 100644 --- a/ElementX/Sources/Screens/LocationSharing/StaticLocationScreenViewModel.swift +++ b/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenViewModel.swift @@ -9,31 +9,44 @@ import Combine import Foundation -typealias StaticLocationScreenViewModelType = StateStoreViewModelV2 +typealias LocationSharingScreenViewModelType = StateStoreViewModelV2 -class StaticLocationScreenViewModel: StaticLocationScreenViewModelType, StaticLocationScreenViewModelProtocol { +class LocationSharingScreenViewModel: LocationSharingScreenViewModelType, LocationSharingScreenViewModelProtocol { + private let roomProxy: JoinedRoomProxyProtocol private let timelineController: TimelineControllerProtocol private let analytics: AnalyticsService private let userIndicatorController: UserIndicatorControllerProtocol - private let actionsSubject: PassthroughSubject = .init() - var actions: AnyPublisher { + private let actionsSubject: PassthroughSubject = .init() + var actions: AnyPublisher { actionsSubject.eraseToAnyPublisher() } - init(interactionMode: StaticLocationInteractionMode, + init(interactionMode: LocationSharingInteractionMode, mapURLBuilder: MapTilerURLBuilderProtocol, + liveLocationSharingEnabled: Bool, + roomProxy: JoinedRoomProxyProtocol, timelineController: TimelineControllerProtocol, analytics: AnalyticsService, - userIndicatorController: UserIndicatorControllerProtocol) { + userIndicatorController: UserIndicatorControllerProtocol, + mediaProvider: MediaProviderProtocol) { + self.roomProxy = roomProxy self.timelineController = timelineController self.analytics = analytics self.userIndicatorController = userIndicatorController - super.init(initialViewState: .init(interactionMode: interactionMode, mapURLBuilder: mapURLBuilder)) + super.init(initialViewState: .init(interactionMode: interactionMode, + mapURLBuilder: mapURLBuilder, + showLiveLocationSharingButton: liveLocationSharingEnabled), + mediaProvider: mediaProvider) + + if interactionMode.canShowAvatar { + updateShownUserProfile(members: roomProxy.membersPublisher.value) + setupSubscriptions() + } } - override func process(viewAction: StaticLocationScreenViewAction) { + override func process(viewAction: LocationSharingScreenViewAction) { switch viewAction { case .close: actionsSubject.send(.close) @@ -58,6 +71,32 @@ class StaticLocationScreenViewModel: StaticLocationScreenViewModelType, StaticLo // MARK: - Private + private func setupSubscriptions() { + roomProxy.membersPublisher.sink { [weak self] members in + self?.updateShownUserProfile(members: members) + } + .store(in: &cancellables) + } + + private func updateShownUserProfile(members: [RoomMemberProxyProtocol]) { + switch state.interactionMode { + case .picker: + if let ownUser = members.first(where: { $0.userID == roomProxy.ownUserID }).map(UserProfileProxy.init) { + state.userProfile = ownUser + } else { + state.userProfile = .init(userID: roomProxy.ownUserID) + } + case .viewStatic(.some(let senderID), _): + if let sender = members.first(where: { $0.userID == senderID }).map(UserProfileProxy.init) { + state.userProfile = sender + } else { + state.userProfile = .init(userID: senderID) + } + default: + state.userProfile = nil + } + } + private func sendLocation(_ geoURI: GeoURI, isUserLocation: Bool) async { guard case .success = await timelineController.sendLocation(body: geoURI.bodyMessage, geoURI: geoURI, diff --git a/ElementX/Sources/Screens/LocationSharing/StaticLocationScreenViewModelProtocol.swift b/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenViewModelProtocol.swift similarity index 56% rename from ElementX/Sources/Screens/LocationSharing/StaticLocationScreenViewModelProtocol.swift rename to ElementX/Sources/Screens/LocationSharing/LocationSharingScreenViewModelProtocol.swift index 4573cdffb..5b6bcd2c1 100644 --- a/ElementX/Sources/Screens/LocationSharing/StaticLocationScreenViewModelProtocol.swift +++ b/ElementX/Sources/Screens/LocationSharing/LocationSharingScreenViewModelProtocol.swift @@ -9,7 +9,7 @@ import Combine @MainActor -protocol StaticLocationScreenViewModelProtocol { - var actions: AnyPublisher { get } - var context: StaticLocationScreenViewModelType.Context { get } +protocol LocationSharingScreenViewModelProtocol { + var actions: AnyPublisher { get } + var context: LocationSharingScreenViewModelType.Context { get } } diff --git a/ElementX/Sources/Screens/LocationSharing/StaticLocationScreenCoordinator.swift b/ElementX/Sources/Screens/LocationSharing/StaticLocationScreenCoordinator.swift deleted file mode 100644 index d5ae3480b..000000000 --- a/ElementX/Sources/Screens/LocationSharing/StaticLocationScreenCoordinator.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// Copyright 2025 Element Creations Ltd. -// Copyright 2023-2025 New Vector Ltd. -// -// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. -// Please see LICENSE files in the repository root for full details. -// - -import Combine -import SwiftUI - -struct StaticLocationScreenCoordinatorParameters { - let interactionMode: StaticLocationInteractionMode - let mapURLBuilder: MapTilerURLBuilderProtocol - let timelineController: TimelineControllerProtocol - let appMediator: AppMediatorProtocol - let analytics: AnalyticsService - let userIndicatorController: UserIndicatorControllerProtocol -} - -enum StaticLocationScreenCoordinatorAction { - case close -} - -final class StaticLocationScreenCoordinator: CoordinatorProtocol { - private let parameters: StaticLocationScreenCoordinatorParameters - private let viewModel: StaticLocationScreenViewModelProtocol - - private let actionsSubject: PassthroughSubject = .init() - private var cancellables = Set() - - var actions: AnyPublisher { - actionsSubject.eraseToAnyPublisher() - } - - init(parameters: StaticLocationScreenCoordinatorParameters) { - self.parameters = parameters - - viewModel = StaticLocationScreenViewModel(interactionMode: parameters.interactionMode, - mapURLBuilder: parameters.mapURLBuilder, - timelineController: parameters.timelineController, - analytics: parameters.analytics, - userIndicatorController: parameters.userIndicatorController) - } - - // MARK: - Public - - func start() { - viewModel.actions.sink { [weak self] action in - guard let self else { return } - switch action { - case .close: - actionsSubject.send(.close) - case .openSystemSettings: - parameters.appMediator.openAppSettings() - } - } - .store(in: &cancellables) - } - - func toPresentable() -> AnyView { - AnyView(StaticLocationScreen(context: viewModel.context)) - } -} diff --git a/ElementX/Sources/Screens/LocationSharing/View/LocationSharingScreen.swift b/ElementX/Sources/Screens/LocationSharing/View/LocationSharingScreen.swift new file mode 100644 index 000000000..1f72d7a7a --- /dev/null +++ b/ElementX/Sources/Screens/LocationSharing/View/LocationSharingScreen.swift @@ -0,0 +1,264 @@ +// +// Copyright 2025 Element Creations Ltd. +// Copyright 2023-2025 New Vector 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 LocationSharingScreen: View { + @Bindable var context: LocationSharingScreenViewModel.Context + + var body: some View { + if context.viewState.isLocationPickerMode { + mainContent + .sheet(isPresented: .constant(true)) { + sharingOptionsSheet + .alert(item: $context.alertInfo) + } + } else { + mainContent + .alert(item: $context.alertInfo) + } + } + + // MARK: - Private + + private var mainContent: some View { + mapView + .ignoresSafeArea(edges: .bottom) + .track(screen: context.viewState.isLocationPickerMode ? .LocationSend : .LocationView) + .navigationTitle(L10n.screenViewLocationTitle) + .navigationBarTitleDisplayMode(.inline) + .toolbar { toolbar } + } + + private var mapView: some View { + ZStack(alignment: .center) { + MapLibreMapView(mapURLBuilder: context.viewState.mapURLBuilder, + options: mapOptions, + showsUserLocationMode: $context.showsUserLocationMode, + error: $context.mapError, + mapCenterCoordinate: $context.mapCenterLocation, + hasLoadedUserLocation: $context.hasLoadedUserLocation, + isLocationAuthorized: $context.isLocationAuthorized, + geolocationUncertainty: $context.geolocationUncertainty) { + context.send(viewAction: .userDidPan) + } + .ignoresSafeArea(.all, edges: mapSafeAreaEdges) + + if context.viewState.isLocationPickerMode { + LocationMarkerView(userProfile: context.viewState.shownUserProfile, mediaProvider: context.mediaProvider) + } + } + .overlay(alignment: .topTrailing) { + centerToUserLocationButton + } + } + + @ToolbarContentBuilder + private var toolbar: some ToolbarContent { + ToolbarItem(placement: context.viewState.showShareAction ? .topBarLeading : .topBarTrailing) { + ToolbarButton(role: .close) { + context.send(viewAction: .close) + } + } + + if context.viewState.showShareAction { + ToolbarItem(placement: .topBarTrailing) { + Button { + context.showShareSheet = true + } label: { + Image(systemName: "square.and.arrow.up") + } + .popover(isPresented: $context.showShareSheet) { shareSheet } + } + } + } + + private var mapOptions: MapLibreMapView.Options { + var annotations: [String: LocationAnnotation] = [:] + if !context.viewState.isLocationPickerMode { + let id = context.viewState.shownUserProfile?.userID ?? UUID().uuidString + let annotation = LocationAnnotation(id: id, + coordinate: context.viewState.initialMapCenter, + anchorPoint: .bottomCenter) { + LocationMarkerView(userProfile: context.viewState.shownUserProfile, mediaProvider: context.mediaProvider) + } + annotations[id] = annotation + } + + return .init(zoomLevel: context.viewState.zoomLevel, + initialZoomLevel: context.viewState.initialZoomLevel, + mapCenter: context.viewState.initialMapCenter, + annotations: annotations) + } + + private var mapSafeAreaEdges: Edge.Set { + context.viewState.isLocationPickerMode ? .horizontal : [.horizontal, .bottom] + } + + @ViewBuilder + private var centerToUseIcon: some View { + if context.viewState.isLocationLoading { + ProgressView() + .tint(.compound.iconPrimary) + .padding(13) + } else { + CompoundIcon(context.viewState.isSharingUserLocation ? \.locationNavigatorCentred : \.locationNavigator) + .foregroundStyle(.compound.iconPrimary) + .padding(13) + } + } + + private var centerToUserLocationButton: some View { + Button { + context.send(viewAction: .centerToUser) + } label: { + if #available(iOS 26.0, *) { + centerToUseIcon + .glassEffect(.regular.interactive(), in: Circle()) + .tint(.compound.bgCanvasDefault) + } else { + centerToUseIcon + .background(.compound.bgCanvasDefault, in: RoundedRectangle(cornerRadius: 6)) + } + } + .disabled(context.viewState.isLocationLoading) + .dynamicTypeSize(.large) + .padding(13) + } + + @ViewBuilder + private var shareSheet: some View { + let location = context.viewState.initialMapCenter + let senderName = context.viewState.shownUserProfile?.displayName + AppActivityView(activityItems: [ShareToMapsAppActivity.MapsAppType.apple.activityURL(for: location, senderName: senderName)], + applicationActivities: ShareToMapsAppActivity.MapsAppType.allCases.map { ShareToMapsAppActivity(type: $0, location: location, senderName: senderName) }) + .edgesIgnoringSafeArea(.bottom) + .presentationDetents([.medium, .large]) + .presentationCompactAdaptation(shareSheetCompactPresentation) + .presentationDragIndicator(.hidden) + } + + private var shareSheetCompactPresentation: PresentationAdaptation { + if #available(iOS 26.0, *) { + .none // ShareLinks use a popover presentation on iOS 26, let it match that. + } else { + .sheet + } + } + + @State private var sharingOptionsSheetHeight: CGFloat = .zero + + private var sharingOptionsSheet: some View { + VStack(alignment: .leading, spacing: 0) { + Button { + context.send(viewAction: .selectLocation) + } label: { + if context.viewState.isSharingUserLocation { + LocationSharingLabel(text: L10n.screenShareMyLocationAction, + icon: \.locationNavigatorCentred, + iconColor: .compound.iconSecondary) + } else { + LocationSharingLabel(text: L10n.screenShareThisLocationAction, + icon: \.locationNavigator, + iconColor: .compound.iconSecondary) + } + } + if context.viewState.showLiveLocationSharingButton { + Button { } label: { + LocationSharingLabel(text: L10n.actionShareLiveLocation, + icon: \.locationPinSolid, + iconColor: .compound.iconAccentPrimary) + } + } + } + .font(.compound.bodyLG) + .foregroundStyle(.compound.textPrimary) + .padding(.top, 38) + .readHeight($sharingOptionsSheetHeight) + .interactiveDismissDisabled() + .presentationBackground(.compound.bgCanvasDefault) + .presentationBackgroundInteraction(.enabled) + .presentationDragIndicator(.hidden) + .presentationDetents([.height(sharingOptionsSheetHeight)]) + } +} + +private struct LocationSharingLabel: View { + let text: String + let icon: KeyPath + let iconColor: Color + + var body: some View { + Label { + Text(text) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.vertical, 14) + .rowDivider(alignment: .top, horizontalInsets: 16.0) + } icon: { + CompoundIcon(icon) + .foregroundStyle(iconColor) + } + .padding(.leading, 16) + .frame(maxWidth: .infinity, alignment: .leading) + } +} + +// MARK: - Previews + +struct LocationSharingScreen_Previews: PreviewProvider, TestablePreview { + static let viewModel = LocationSharingScreenViewModel(interactionMode: .viewStatic(senderID: "@dan:matrix.org", geoURI: .init(latitude: 41.9027835, + longitude: 12.4963655)), + mapURLBuilder: ServiceLocator.shared.settings.mapTilerConfiguration, + liveLocationSharingEnabled: true, + roomProxy: JoinedRoomProxyMock(.init()), + timelineController: MockTimelineController(), + analytics: ServiceLocator.shared.analytics, + userIndicatorController: UserIndicatorControllerMock(), + mediaProvider: MediaProviderMock(configuration: .init())) + + static let pinViewModel = LocationSharingScreenViewModel(interactionMode: .viewStatic(senderID: nil, geoURI: .init(latitude: 41.9027835, + longitude: 12.4963655)), + mapURLBuilder: ServiceLocator.shared.settings.mapTilerConfiguration, + liveLocationSharingEnabled: true, + roomProxy: JoinedRoomProxyMock(.init()), + timelineController: MockTimelineController(), + analytics: ServiceLocator.shared.analytics, + userIndicatorController: UserIndicatorControllerMock(), + mediaProvider: MediaProviderMock(configuration: .init())) + + static let pickerViewModel = LocationSharingScreenViewModel(interactionMode: .picker, + mapURLBuilder: ServiceLocator.shared.settings.mapTilerConfiguration, + liveLocationSharingEnabled: true, + roomProxy: JoinedRoomProxyMock(.init()), + timelineController: MockTimelineController(), + analytics: ServiceLocator.shared.analytics, + userIndicatorController: UserIndicatorControllerMock(), + mediaProvider: MediaProviderMock(configuration: .init())) + + static var previews: some View { + ElementNavigationStack { + LocationSharingScreen(context: pickerViewModel.context) + } + .previewDisplayName("Picker") + + ElementNavigationStack { + LocationSharingScreen(context: viewModel.context) + } + .previewDisplayName("User Static Location") + + ElementNavigationStack { + LocationSharingScreen(context: pinViewModel.context) + } + .previewDisplayName("Pin Static Location") + } +} + +private extension CGPoint { + static let bottomCenter: Self = .init(x: 0.5, y: 1) +} diff --git a/ElementX/Sources/Screens/LocationSharing/View/StaticLocationScreen.swift b/ElementX/Sources/Screens/LocationSharing/View/StaticLocationScreen.swift deleted file mode 100644 index 0a6f9d89a..000000000 --- a/ElementX/Sources/Screens/LocationSharing/View/StaticLocationScreen.swift +++ /dev/null @@ -1,198 +0,0 @@ -// -// Copyright 2025 Element Creations Ltd. -// Copyright 2023-2025 New Vector 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 StaticLocationScreen: View { - @Bindable var context: StaticLocationScreenViewModel.Context - - var body: some View { - VStack(spacing: 0) { - if let locationDescription = context.viewState.locationDescription { - Text(locationDescription) - .lineLimit(2) - .foregroundColor(Color.compound.textPrimary) - .font(.compound.bodyMD) - .padding(.horizontal, 16) - .padding(.vertical, 8) - } - mapView - } - .track(screen: context.viewState.isLocationPickerMode ? .LocationSend : .LocationView) - .navigationTitle(context.viewState.navigationTitle) - .navigationBarTitleDisplayMode(.inline) - .toolbar { toolbar } - .alert(item: $context.alertInfo) - } - - private var mapView: some View { - ZStack(alignment: .center) { - MapLibreMapView(mapURLBuilder: context.viewState.mapURLBuilder, - options: mapOptions, - showsUserLocationMode: $context.showsUserLocationMode, - error: $context.mapError, - mapCenterCoordinate: $context.mapCenterLocation, - isLocationAuthorized: $context.isLocationAuthorized, - geolocationUncertainty: $context.geolocationUncertainty) { - context.send(viewAction: .userDidPan) - } - .ignoresSafeArea(.all, edges: mapSafeAreaEdges) - - if context.viewState.isLocationPickerMode { - LocationMarkerView() - } - } - .overlay(alignment: .bottomTrailing) { - centerToUserLocationButton - } - } - - // MARK: - Private - - @ToolbarContentBuilder - private var toolbar: some ToolbarContent { - ToolbarItem(placement: .navigationBarLeading) { - closeButton - } - - if context.viewState.showShareAction { - ToolbarItem(placement: .navigationBarTrailing) { - shareButton - .popover(isPresented: $context.showShareSheet) { shareSheet } - } - } - - if context.viewState.isLocationPickerMode { - ToolbarItemGroup(placement: .bottomBar) { - selectLocationButton - Spacer() - } - } - } - - private var mapOptions: MapLibreMapView.Options { - var annotations: [LocationAnnotation] = [] - if context.viewState.isLocationPickerMode == false { - let annotation = LocationAnnotation(coordinate: context.viewState.initialMapCenter, anchorPoint: .bottomCenter) { - LocationMarkerView() - } - annotations.append(annotation) - } - - return .init(zoomLevel: context.viewState.zoomLevel, - initialZoomLevel: context.viewState.initialZoomLevel, - mapCenter: context.viewState.initialMapCenter, - annotations: annotations) - } - - private var mapSafeAreaEdges: Edge.Set { - context.viewState.isLocationPickerMode ? .horizontal : [.horizontal, .bottom] - } - - private var selectLocationButton: some View { - Button { - context.send(viewAction: .selectLocation) - } label: { - HStack(spacing: 8) { - CompoundIcon(\.shareIos) - Text(context.viewState.isSharingUserLocation ? L10n.screenShareMyLocationAction : L10n.screenShareThisLocationAction) - } - } - } - - private var centerToUserLocationButton: some View { - Button { - context.send(viewAction: .centerToUser) - } label: { - CompoundIcon(context.viewState.isSharingUserLocation ? \.locationNavigatorCentred : \.locationNavigator) - .padding(8) - .background(.compound.bgCanvasDefault, in: RoundedRectangle(cornerRadius: 6)) - } - .dynamicTypeSize(.large) - .padding(16) - } - - private var closeButton: some View { - Button(L10n.actionCancel) { - context.send(viewAction: .close) - } - } - - private var shareButton: some View { - Button { - context.showShareSheet = true - } label: { - Image(systemName: "square.and.arrow.up") - } - } - - @ViewBuilder - private var shareSheet: some View { - let location = context.viewState.initialMapCenter - let locationDescription = context.viewState.locationDescription - AppActivityView(activityItems: [ShareToMapsAppActivity.MapsAppType.apple.activityURL(for: location, locationDescription: locationDescription)], - applicationActivities: ShareToMapsAppActivity.MapsAppType.allCases.map { ShareToMapsAppActivity(type: $0, location: location, locationDescription: locationDescription) }) - .edgesIgnoringSafeArea(.bottom) - .presentationDetents([.medium, .large]) - .presentationCompactAdaptation(shareSheetCompactPresentation) - .presentationDragIndicator(.hidden) - } - - private var shareSheetCompactPresentation: PresentationAdaptation { - if #available(iOS 26.0, *) { - .none // ShareLinks use a popover presentation on iOS 26, let it match that. - } else { - .sheet - } - } -} - -// MARK: - Previews - -struct StaticLocationScreenViewer_Previews: PreviewProvider, TestablePreview { - static let viewModel = StaticLocationScreenViewModel(interactionMode: .viewOnly(geoURI: .init(latitude: 41.9027835, - longitude: 12.4963655)), - mapURLBuilder: ServiceLocator.shared.settings.mapTilerConfiguration, - timelineController: MockTimelineController(), - analytics: ServiceLocator.shared.analytics, - userIndicatorController: UserIndicatorControllerMock()) - static let pickerViewModel = StaticLocationScreenViewModel(interactionMode: .picker, - mapURLBuilder: ServiceLocator.shared.settings.mapTilerConfiguration, - timelineController: MockTimelineController(), - analytics: ServiceLocator.shared.analytics, - userIndicatorController: UserIndicatorControllerMock()) - static let descriptionViewModel = StaticLocationScreenViewModel(interactionMode: .viewOnly(geoURI: .init(latitude: 41.9027835, - longitude: 12.4963655), - description: "Cool position"), - mapURLBuilder: ServiceLocator.shared.settings.mapTilerConfiguration, - timelineController: MockTimelineController(), - analytics: ServiceLocator.shared.analytics, - userIndicatorController: UserIndicatorControllerMock()) - - static var previews: some View { - ElementNavigationStack { - StaticLocationScreen(context: pickerViewModel.context) - } - .previewDisplayName("Picker") - - ElementNavigationStack { - StaticLocationScreen(context: viewModel.context) - } - .previewDisplayName("View Only") - - ElementNavigationStack { - StaticLocationScreen(context: descriptionViewModel.context) - } - .previewDisplayName("View Only (with description)") - } -} - -private extension CGPoint { - static let bottomCenter: Self = .init(x: 0.5, y: 1) -} diff --git a/ElementX/Sources/Screens/PinnedEventsTimelineScreen/PinnedEventsTimelineScreenCoordinator.swift b/ElementX/Sources/Screens/PinnedEventsTimelineScreen/PinnedEventsTimelineScreenCoordinator.swift index e33c19a78..04bcf000a 100644 --- a/ElementX/Sources/Screens/PinnedEventsTimelineScreen/PinnedEventsTimelineScreenCoordinator.swift +++ b/ElementX/Sources/Screens/PinnedEventsTimelineScreen/PinnedEventsTimelineScreenCoordinator.swift @@ -26,7 +26,7 @@ struct PinnedEventsTimelineScreenCoordinatorParameters { enum PinnedEventsTimelineScreenCoordinatorAction { case dismiss case displayUser(userID: String) - case presentLocationViewer(geoURI: GeoURI, description: String?) + case presentLocationViewer(senderID: String?, geoURI: GeoURI) case displayMessageForwarding(forwardingItem: MessageForwardingItem) case displayRoomScreenWithFocussedPin(eventID: String, threadRootEventID: String?) } @@ -87,8 +87,9 @@ final class PinnedEventsTimelineScreenCoordinator: CoordinatorProtocol { actionsSubject.send(.displayMessageForwarding(forwardingItem: forwardingItem)) case .displayMediaPreview(let mediaPreviewViewModel): viewModel.displayMediaPreview(mediaPreviewViewModel) - case .displayLocation(_, let geoURI, let description): - actionsSubject.send(.presentLocationViewer(geoURI: geoURI, description: description)) + case .displayLocation(let senderID, let geoURI): + actionsSubject.send(.presentLocationViewer(senderID: senderID, + geoURI: geoURI)) case .viewInRoomTimeline(let eventID, let threadRootEventID): actionsSubject.send(.displayRoomScreenWithFocussedPin(eventID: eventID, threadRootEventID: threadRootEventID)) // These other actions will not be handled in this view diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift index c482cca60..a3cb718dd 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift @@ -39,7 +39,7 @@ enum RoomScreenCoordinatorAction { case presentRoomDetails case presentLocationPicker case presentPollForm(mode: PollFormMode) - case presentLocationViewer(body: String, geoURI: GeoURI, description: String?) + case presentLocationViewer(senderID: String?, geoURI: GeoURI) case presentEmojiPicker(itemID: TimelineItemIdentifier, selectedEmojis: Set) case presentRoomMemberDetails(userID: String) case presentMessageForwarding(forwardingItem: MessageForwardingItem) @@ -137,8 +137,8 @@ final class RoomScreenCoordinator: CoordinatorProtocol { actionsSubject.send(.presentRoomMemberDetails(userID: userID)) case .displayMessageForwarding(let forwardingItem): actionsSubject.send(.presentMessageForwarding(forwardingItem: forwardingItem)) - case .displayLocation(let body, let geoURI, let description): - actionsSubject.send(.presentLocationViewer(body: body, geoURI: geoURI, description: description)) + case .displayLocation(let senderID, let geoURI): + actionsSubject.send(.presentLocationViewer(senderID: senderID, geoURI: geoURI)) case .displayResolveSendFailure(let failure, let sendHandle): actionsSubject.send(.presentResolveSendFailure(failure: failure, sendHandle: sendHandle)) case .displayThread(let itemID): diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift index e61f923bd..9e8426902 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift @@ -67,6 +67,8 @@ protocol DeveloperOptionsProtocol: AnyObject { var linkPreviewsEnabled: Bool { get set } var linkNewDeviceEnabled: Bool { get set } + + var liveLocationSharingEnabled: Bool { get set } } extension AppSettings: DeveloperOptionsProtocol { } diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift index a59ffb996..4f8e2671c 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift @@ -76,6 +76,10 @@ struct DeveloperOptionsScreen: View { Text("Can leak the device IP address when loading link metadata.") .foregroundStyle(.compound.textCriticalPrimary) } + + Toggle(isOn: $context.liveLocationSharingEnabled) { + Text("Live location sharing") + } } Section("Join rules") { diff --git a/ElementX/Sources/Screens/ThreadTimelineScreen/ThreadTimelineScreenCoordinator.swift b/ElementX/Sources/Screens/ThreadTimelineScreen/ThreadTimelineScreenCoordinator.swift index af304f393..b0dbc1cb6 100644 --- a/ElementX/Sources/Screens/ThreadTimelineScreen/ThreadTimelineScreenCoordinator.swift +++ b/ElementX/Sources/Screens/ThreadTimelineScreen/ThreadTimelineScreenCoordinator.swift @@ -33,7 +33,7 @@ enum ThreadTimelineScreenCoordinatorAction { case presentMediaUploadPicker(mode: MediaPickerScreenMode) case presentMediaUploadPreviewScreen(mediaURLs: [URL]) case presentLocationPicker - case presentLocationViewer(body: String, geoURI: GeoURI, description: String?) + case presentLocationViewer(senderID: String?, geoURI: GeoURI) case presentPollForm(mode: PollFormMode) case presentEmojiPicker(itemID: TimelineItemIdentifier, selectedEmojis: Set) case presentRoomMemberDetails(userID: String) @@ -118,10 +118,9 @@ final class ThreadTimelineScreenCoordinator: CoordinatorProtocol { viewModel.displayMediaPreview(mediaPreviewViewModel) case .displayLocationPicker: actionsSubject.send(.presentLocationPicker) - case .displayLocation(let body, let geoURI, let description): - actionsSubject.send(.presentLocationViewer(body: body, - geoURI: geoURI, - description: description)) + case .displayLocation(let senderID, let geoURI): + actionsSubject.send(.presentLocationViewer(senderID: senderID, + geoURI: geoURI)) case .displayPollForm(let mode): actionsSubject.send(.presentPollForm(mode: mode)) case .displayMediaUploadPreviewScreen(let mediaURLs): diff --git a/ElementX/Sources/Screens/Timeline/TimelineInteractionHandler.swift b/ElementX/Sources/Screens/Timeline/TimelineInteractionHandler.swift index 414890362..05a59364e 100644 --- a/ElementX/Sources/Screens/Timeline/TimelineInteractionHandler.swift +++ b/ElementX/Sources/Screens/Timeline/TimelineInteractionHandler.swift @@ -536,7 +536,8 @@ class TimelineInteractionHandler { switch timelineItem { case let item as LocationRoomTimelineItem: guard let geoURI = item.content.geoURI else { return .none } - return .displayLocation(body: item.content.body, geoURI: geoURI, description: item.content.description) + return .displayLocation(senderID: item.content.kind == .sender ? item.sender.id : nil, + geoURI: geoURI) case is ImageRoomTimelineItem, is VideoRoomTimelineItem: return await mediaPreviewAction(for: timelineItem, messageTypes: [.image, .video]) diff --git a/ElementX/Sources/Screens/Timeline/TimelineModels.swift b/ElementX/Sources/Screens/Timeline/TimelineModels.swift index 186a5ecf5..02d03c52f 100644 --- a/ElementX/Sources/Screens/Timeline/TimelineModels.swift +++ b/ElementX/Sources/Screens/Timeline/TimelineModels.swift @@ -23,7 +23,7 @@ enum TimelineViewModelAction { case displaySenderDetails(userID: String) case displayMessageForwarding(forwardingItem: MessageForwardingItem) case displayMediaPreview(TimelineMediaPreviewViewModel) - case displayLocation(body: String, geoURI: GeoURI, description: String?) + case displayLocation(senderID: String?, geoURI: GeoURI) case displayResolveSendFailure(failure: TimelineItemSendFailure.VerifiedUser, sendHandle: SendHandleProxy) case displayThread(itemID: TimelineItemIdentifier) case composer(action: TimelineComposerAction) diff --git a/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift b/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift index a3d6f1666..b625a774d 100644 --- a/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift +++ b/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift @@ -655,8 +655,8 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol { let mediaPreviewViewModel = makeMediaPreviewViewModel(item: item, timelineViewModelKind: timelineViewModelKind) actionsSubject.send(.displayMediaPreview(mediaPreviewViewModel)) - case .displayLocation(let body, let geoURI, let description): - actionsSubject.send(.displayLocation(body: body, geoURI: geoURI, description: description)) + case .displayLocation(let senderID, let geoURI): + actionsSubject.send(.displayLocation(senderID: senderID, geoURI: geoURI)) case .none: break } diff --git a/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbledStylerView.swift b/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbledStylerView.swift index 2949f8184..b565f16cc 100644 --- a/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbledStylerView.swift +++ b/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbledStylerView.swift @@ -705,20 +705,7 @@ private struct MockTimelineContent: View { sender: .init(id: "Bob"), content: .init(body: "Fallback geo uri description", geoURI: .init(latitude: 41.902782, - longitude: 12.496366), - description: "Location description description description description description description description description"), - properties: .init(replyDetails: replyDetails, - isThreaded: isThreaded, - threadSummary: threadSummary))) - - LocationRoomTimelineView(timelineItem: .init(id: makeItemIdentifier(), - timestamp: .mock, - isOutgoing: false, - isEditable: false, - canBeRepliedTo: true, - sender: .init(id: "Bob"), - content: .init(body: "Fallback geo uri description", - geoURI: .init(latitude: 41.902782, longitude: 12.496366), description: nil), + longitude: 12.496366)), properties: .init(replyDetails: replyDetails, isThreaded: isThreaded, threadSummary: threadSummary))) diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/LocationRoomTimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/LocationRoomTimelineView.swift index 41bf6a39a..55c7a4dbd 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/LocationRoomTimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/LocationRoomTimelineView.swift @@ -16,7 +16,7 @@ struct LocationRoomTimelineView: View { TimelineStyler(timelineItem: timelineItem) { mainContent .accessibilityElement(children: .ignore) - .accessibilityLabel(accessibilityLabel) + .accessibilityLabel(L10n.commonSharedLocation) .onTapGesture { guard context.viewState.mapTilerConfiguration.isEnabled else { return } context.send(viewAction: .mediaTapped(itemID: timelineItem.id)) @@ -27,41 +27,20 @@ struct LocationRoomTimelineView: View { @ViewBuilder private var mainContent: some View { if let geoURI = timelineItem.content.geoURI { - VStack(alignment: .leading, spacing: 0) { - descriptionView - .frame(maxWidth: mapAspectRatio * mapMaxHeight, alignment: .leading) - - MapLibreStaticMapView(geoURI: geoURI, - mapURLBuilder: context.viewState.mapTilerConfiguration, - mapSize: .init(width: mapAspectRatio * mapMaxHeight, height: mapMaxHeight)) { - LocationMarkerView() - } - .frame(maxHeight: mapMaxHeight) - .aspectRatio(mapAspectRatio, contentMode: .fit) - .clipped() + MapLibreStaticMapView(geoURI: geoURI, + mapURLBuilder: context.viewState.mapTilerConfiguration, + mapSize: .init(width: mapAspectRatio * mapMaxHeight, height: mapMaxHeight)) { + LocationMarkerView(userProfile: timelineItem.content.kind == .sender ? .init(sender: timelineItem.sender) : nil, mediaProvider: context.mediaProvider) } + .frame(maxHeight: mapMaxHeight) + .aspectRatio(mapAspectRatio, contentMode: .fit) + .clipped() } else { FormattedBodyText(text: timelineItem.body, additionalWhitespacesCount: timelineItem.additionalWhitespaces()) } } // MARK: - Private - - private var accessibilityLabel: String { - if let description = timelineItem.content.description { - return "\(L10n.commonSharedLocation), \(description)" - } - - return L10n.commonSharedLocation - } - - @ViewBuilder - private var descriptionView: some View { - if let description = timelineItem.content.description, !description.isEmpty { - FormattedBodyText(text: description) - .padding(8) - } - } private let mapAspectRatio: Double = 3 / 2 private let mapMaxHeight: Double = 300 @@ -107,9 +86,9 @@ struct LocationRoomTimelineView_Previews: PreviewProvider, TestablePreview { isOutgoing: false, isEditable: false, canBeRepliedTo: true, - sender: .init(id: "Bob"), + sender: .init(id: "@bob:matrix.org", displayName: "Bob", avatarURL: .mockMXCUserAvatar), content: .init(body: "Fallback geo uri description", - geoURI: .init(latitude: 41.902782, longitude: 12.496366), description: "Location description description description description description description description description"))) + geoURI: .init(latitude: 41.902782, longitude: 12.496366)))) LocationRoomTimelineView(timelineItem: .init(id: .randomEvent, timestamp: .mock, isOutgoing: false, @@ -117,7 +96,8 @@ struct LocationRoomTimelineView_Previews: PreviewProvider, TestablePreview { canBeRepliedTo: true, sender: .init(id: "Bob"), content: .init(body: "Fallback geo uri description", - geoURI: .init(latitude: 41.902782, longitude: 12.496366), description: "Location description description description description description description description description"), + geoURI: .init(latitude: 41.902782, longitude: 12.496366), + kind: .pin), properties: .init(replyDetails: .loaded(sender: .init(id: "Someone"), eventID: "123", eventContent: .message(.text(.init(body: "The thread content goes 'ere.")))), diff --git a/ElementX/Sources/Services/Timeline/TimelineController/TimelineControllerProtocol.swift b/ElementX/Sources/Services/Timeline/TimelineController/TimelineControllerProtocol.swift index e7cb7762b..4e096531b 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/TimelineControllerProtocol.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/TimelineControllerProtocol.swift @@ -25,7 +25,7 @@ enum TimelineControllerAction { } case displayMediaPreview(item: EventBasedMessageTimelineItemProtocol, timelineViewModel: TimelineViewModelKind) - case displayLocation(body: String, geoURI: GeoURI, description: String?) + case displayLocation(senderID: String?, geoURI: GeoURI) case none } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/LocationRoomTimelineItemContent.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/LocationRoomTimelineItemContent.swift index a13a411c3..59ee6c491 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/LocationRoomTimelineItemContent.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/LocationRoomTimelineItemContent.swift @@ -6,16 +6,32 @@ // Please see LICENSE files in the repository root for full details. // +import MatrixRustSDK + struct LocationRoomTimelineItemContent: Hashable { + enum Kind { + case sender + case pin + + init(from asset: AssetType?) { + self = switch asset { + case .pin: + .pin + case .sender, .none: + .sender + } + } + } + let body: String let geoURI: GeoURI? - let description: String? + let kind: Kind init(body: String, geoURI: GeoURI? = nil, - description: String? = nil) { + kind: Kind = .sender) { self.body = body self.geoURI = geoURI - self.description = description + self.kind = kind } } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift index e49696708..dbc8cb1fa 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift @@ -583,7 +583,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { private func buildLocationTimelineItemContent(_ locationContent: LocationContent) -> LocationRoomTimelineItemContent { LocationRoomTimelineItemContent(body: locationContent.body, geoURI: .init(string: locationContent.geoUri), - description: locationContent.description) + kind: .init(from: locationContent.asset)) } private func buildFileTimelineItemContent(_ messageContent: FileMessageContent) -> FileRoomTimelineItemContent { diff --git a/ElementX/Sources/Services/Users/UserProfileProxy.swift b/ElementX/Sources/Services/Users/UserProfileProxy.swift index 14c66fc67..b4e544fad 100644 --- a/ElementX/Sources/Services/Users/UserProfileProxy.swift +++ b/ElementX/Sources/Services/Users/UserProfileProxy.swift @@ -44,6 +44,10 @@ struct UserProfileProxy: Equatable, Hashable { avatarURL = sdkRoomHero.avatarUrl.flatMap(URL.init(string:)) } + init(member: RoomMemberProxyProtocol) { + self.init(member: RoomMemberDetails(withProxy: member)) + } + /// A user is meant to be "verified" when the GET profile returns back either the display name or the avatar /// If isn't we aren't sure that the related matrix id really exists. var isVerified: Bool { diff --git a/PreviewTests/Sources/GeneratedPreviewTests.swift b/PreviewTests/Sources/GeneratedPreviewTests.swift index 074fb44fb..3603cc107 100644 --- a/PreviewTests/Sources/GeneratedPreviewTests.swift +++ b/PreviewTests/Sources/GeneratedPreviewTests.swift @@ -603,6 +603,14 @@ extension PreviewTests { } } + @Test + func locationSharingScreen() async throws { + AppSettings.resetAllSettings() // Ensure this test's previews start with fresh settings. + for (index, preview) in LocationSharingScreen_Previews._allPreviews.enumerated() { + try await assertSnapshots(matching: preview, step: index) + } + } + @Test func loginScreen() async throws { AppSettings.resetAllSettings() // Ensure this test's previews start with fresh settings. @@ -1387,14 +1395,6 @@ extension PreviewTests { } } - @Test - func staticLocationScreenViewer() async throws { - AppSettings.resetAllSettings() // Ensure this test's previews start with fresh settings. - for (index, preview) in StaticLocationScreenViewer_Previews._allPreviews.enumerated() { - try await assertSnapshots(matching: preview, step: index) - } - } - @Test func stickerRoomTimelineView() async throws { AppSettings.resetAllSettings() // Ensure this test's previews start with fresh settings. diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationMarkerView.iPad-en-GB-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationMarkerView.iPad-en-GB-0.png index bb7d99079..5a5a1484f 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationMarkerView.iPad-en-GB-0.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationMarkerView.iPad-en-GB-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:43c010abad7474002e5830bf5332ae068c82deaa789ba26f0aa3cb26d61edb25 -size 82843 +oid sha256:c00139208711b767a8f19c563bc7cef80314c3382ce5ce7d481a71d5ef70df1a +size 69042 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationMarkerView.iPad-pseudo-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationMarkerView.iPad-pseudo-0.png index bb7d99079..5a5a1484f 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationMarkerView.iPad-pseudo-0.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationMarkerView.iPad-pseudo-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:43c010abad7474002e5830bf5332ae068c82deaa789ba26f0aa3cb26d61edb25 -size 82843 +oid sha256:c00139208711b767a8f19c563bc7cef80314c3382ce5ce7d481a71d5ef70df1a +size 69042 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationMarkerView.iPhone-en-GB-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationMarkerView.iPhone-en-GB-0.png index d715d5670..fbc577fab 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationMarkerView.iPhone-en-GB-0.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationMarkerView.iPhone-en-GB-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6275a174efe2615503282742972c952b8a66e0d00bfbb806b8eea3f2ffee9b27 -size 40724 +oid sha256:5dcaa6876ed9b9ef600f3c8327aed5a1b5e87cfc3990994d2b44d89973597c2f +size 51341 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationMarkerView.iPhone-pseudo-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationMarkerView.iPhone-pseudo-0.png index d715d5670..fbc577fab 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationMarkerView.iPhone-pseudo-0.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationMarkerView.iPhone-pseudo-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6275a174efe2615503282742972c952b8a66e0d00bfbb806b8eea3f2ffee9b27 -size 40724 +oid sha256:5dcaa6876ed9b9ef600f3c8327aed5a1b5e87cfc3990994d2b44d89973597c2f +size 51341 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationRoomTimelineView.Bubbles-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationRoomTimelineView.Bubbles-iPad-en-GB.png index 45b875ee9..2a92b09b9 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationRoomTimelineView.Bubbles-iPad-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationRoomTimelineView.Bubbles-iPad-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7fb62383066a45ae514f8736f6ea08b8a5c0bab0c231836613ff57d1ddb95975 -size 1027619 +oid sha256:71845ef91726086a8b88816e8535cbf5bbdd82e0de0d5a0ca9ebca75c376944b +size 1227270 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationRoomTimelineView.Bubbles-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationRoomTimelineView.Bubbles-iPad-pseudo.png index c16096405..54d5a0eca 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationRoomTimelineView.Bubbles-iPad-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationRoomTimelineView.Bubbles-iPad-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8575f49f01e82cbca9366dab9b31954df18e0c8a2fc267225fc3ca9d93286695 -size 1029185 +oid sha256:9a8b87ca66a0447bf808b4ef2d67569f1a88fbc798073eefd56fcb39d4f126d3 +size 1228934 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationRoomTimelineView.Bubbles-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationRoomTimelineView.Bubbles-iPhone-en-GB.png index a98082710..6cfaeb320 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationRoomTimelineView.Bubbles-iPhone-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationRoomTimelineView.Bubbles-iPhone-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:667d2b653d460a351e8c9b1adf708532d215b465ecfe3548409a1de3abc12ba5 -size 607504 +oid sha256:edeeccdfbfb21821ad3eb56836d92e78e2f36516ffbf8cdd9ab7b068a9b2b68c +size 580022 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationRoomTimelineView.Bubbles-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationRoomTimelineView.Bubbles-iPhone-pseudo.png index c33726c2d..cce704459 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationRoomTimelineView.Bubbles-iPhone-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationRoomTimelineView.Bubbles-iPhone-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:547c133de43262b6e4a3a91231af8ed39af98357dd7962df14dc8011379100a7 -size 580526 +oid sha256:410475fefb08e8e59d5f5e6c3b25a6ce8d220dd819a51708ead024754f4ece73 +size 580183 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.Picker-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.Picker-iPad-en-GB.png new file mode 100644 index 000000000..3fa30dbfb --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.Picker-iPad-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64737509400fe27cdb017b8f66cffe8c0807f958827cff84a9ce5484db0ca3fc +size 80109 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.Picker-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.Picker-iPad-pseudo.png new file mode 100644 index 000000000..3fa30dbfb --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.Picker-iPad-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64737509400fe27cdb017b8f66cffe8c0807f958827cff84a9ce5484db0ca3fc +size 80109 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.Picker-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.Picker-iPhone-en-GB.png new file mode 100644 index 000000000..95519c8ea --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.Picker-iPhone-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:08e72ae0c7d34108690371b29952267b1ac06bb62f203515f3c5e4143efd2ca8 +size 38511 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.Picker-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.Picker-iPhone-pseudo.png new file mode 100644 index 000000000..95519c8ea --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.Picker-iPhone-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:08e72ae0c7d34108690371b29952267b1ac06bb62f203515f3c5e4143efd2ca8 +size 38511 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.Pin-Static-Location-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.Pin-Static-Location-iPad-en-GB.png new file mode 100644 index 000000000..5b28befe4 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.Pin-Static-Location-iPad-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cbf4a51ed98c176336fc2e5be99311342bdae0d931d5d78dabca0ace3dd3a85b +size 80783 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.Pin-Static-Location-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.Pin-Static-Location-iPad-pseudo.png new file mode 100644 index 000000000..5b28befe4 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.Pin-Static-Location-iPad-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cbf4a51ed98c176336fc2e5be99311342bdae0d931d5d78dabca0ace3dd3a85b +size 80783 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.Pin-Static-Location-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.Pin-Static-Location-iPhone-en-GB.png new file mode 100644 index 000000000..8e2bbfa03 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.Pin-Static-Location-iPhone-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:117ab2273ccfba15cee5546e521a85b87c8f7b915b869a299234c7d1a0caecd2 +size 41622 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.Pin-Static-Location-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.Pin-Static-Location-iPhone-pseudo.png new file mode 100644 index 000000000..8e2bbfa03 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.Pin-Static-Location-iPhone-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:117ab2273ccfba15cee5546e521a85b87c8f7b915b869a299234c7d1a0caecd2 +size 41622 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.User-Static-Location-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.User-Static-Location-iPad-en-GB.png new file mode 100644 index 000000000..7612969bb --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.User-Static-Location-iPad-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4635f42e9d912d20f4963c5c3627f543cb00f95183fa0e902c9cc61b1afc921 +size 93073 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.User-Static-Location-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.User-Static-Location-iPad-pseudo.png new file mode 100644 index 000000000..7612969bb --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.User-Static-Location-iPad-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4635f42e9d912d20f4963c5c3627f543cb00f95183fa0e902c9cc61b1afc921 +size 93073 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.User-Static-Location-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.User-Static-Location-iPhone-en-GB.png new file mode 100644 index 000000000..b5c8cb0be --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.User-Static-Location-iPhone-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c68d58d06123afaa7e02fc798b2360e1818c450fd277b275308d5704203e5547 +size 53102 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.User-Static-Location-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.User-Static-Location-iPhone-pseudo.png new file mode 100644 index 000000000..b5c8cb0be --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/locationSharingScreen.User-Static-Location-iPhone-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c68d58d06123afaa7e02fc798b2360e1818c450fd277b275308d5704203e5547 +size 53102 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.Picker-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.Picker-iPad-en-GB.png deleted file mode 100644 index 6c8179284..000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.Picker-iPad-en-GB.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7fe3be6ac7144e77a64deea2ec9abe990b7066df18bf693018e3cbbac95f7b19 -size 92909 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.Picker-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.Picker-iPad-pseudo.png deleted file mode 100644 index 99956db47..000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.Picker-iPad-pseudo.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:29136dedce23b9175a539c68f190f339972cfeedd51714b2d7dbcd487c0918c1 -size 94512 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.Picker-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.Picker-iPhone-en-GB.png deleted file mode 100644 index 9822eecfc..000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.Picker-iPhone-en-GB.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dd7467f8fecb694a44d88e404999373428dd5b0909f9a821a8cbc9961f132b59 -size 46905 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.Picker-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.Picker-iPhone-pseudo.png deleted file mode 100644 index 086a0b733..000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.Picker-iPhone-pseudo.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c3cd2e0746b0f78394304c614d9a80cd15392986a58b4b64eaa36757997aff42 -size 48123 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.View-Only-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.View-Only-iPad-en-GB.png deleted file mode 100644 index 8389bc0fa..000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.View-Only-iPad-en-GB.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1fed42a97d568cc0d024afb9be2c7c8ae2359a88f1335b307ab5b8cbe0929f59 -size 88451 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.View-Only-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.View-Only-iPad-pseudo.png deleted file mode 100644 index b7b557abe..000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.View-Only-iPad-pseudo.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:79e7bdc97b95baf9ec65240a09543d1efa7a6f0ee8178a0ad24ec7fa25f4fbcf -size 88813 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.View-Only-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.View-Only-iPhone-en-GB.png deleted file mode 100644 index 83b007d71..000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.View-Only-iPhone-en-GB.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8dc6d6f2dcdac244d56349c4d432e43c281e539a83769e4711ebfa43d5b62462 -size 42173 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.View-Only-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.View-Only-iPhone-pseudo.png deleted file mode 100644 index 83b007d71..000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.View-Only-iPhone-pseudo.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8dc6d6f2dcdac244d56349c4d432e43c281e539a83769e4711ebfa43d5b62462 -size 42173 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.View-Only-with-description-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.View-Only-with-description-iPad-en-GB.png deleted file mode 100644 index 0a947c5cf..000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.View-Only-with-description-iPad-en-GB.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3ec7064466b5a91fd490c9aebdcfb36efbc2a585b86998725bcd90e2c280c726 -size 91675 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.View-Only-with-description-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.View-Only-with-description-iPad-pseudo.png deleted file mode 100644 index 41496ee0f..000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.View-Only-with-description-iPad-pseudo.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:241dd732058072774947a9aa0dbd7c6bf410608ba8b4e1733942161feb7d725e -size 92048 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.View-Only-with-description-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.View-Only-with-description-iPhone-en-GB.png deleted file mode 100644 index b0f360a38..000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.View-Only-with-description-iPhone-en-GB.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:763523520732699428fcf758b36119ff450d13b6f69b250e5c977651ae06d05e -size 45091 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.View-Only-with-description-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.View-Only-with-description-iPhone-pseudo.png deleted file mode 100644 index b0f360a38..000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/staticLocationScreenViewer.View-Only-with-description-iPhone-pseudo.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:763523520732699428fcf758b36119ff450d13b6f69b250e5c977651ae06d05e -size 45091 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Pinned-messages-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Pinned-messages-iPad-en-GB.png index 68a643f4e..765715d58 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Pinned-messages-iPad-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Pinned-messages-iPad-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d4c2e14e3362efa21a236c61fa17fe94c14f08c35965cc68cc7375e837b63e6e -size 2459797 +oid sha256:7905d4da975ca2f8c8ba13afc3112081360e1c2586e780e0bf7f63e79f11e907 +size 1816558 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Pinned-messages-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Pinned-messages-iPad-pseudo.png index 2a146f029..5c00ef5f0 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Pinned-messages-iPad-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Pinned-messages-iPad-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3bed8adcdeed91791208203243bec9edab8c6cf233538382a6775e5b997f2970 -size 2458950 +oid sha256:6f225a517befd7486ca940036f0b32b051471e6179dddc1afec04027f0e5a34b +size 1815989 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Pinned-messages-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Pinned-messages-iPhone-en-GB.png index f41e486d8..c07ce9239 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Pinned-messages-iPhone-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Pinned-messages-iPhone-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:39e3c3ea4d8f07c295f6c716e568e4900c4fe3977ac008728d63a5679f8f1099 -size 1032059 +oid sha256:711bdb8bba6cf0b1148695e599599a34b137bfb41577ccf53f70402c188f7336 +size 762444 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Pinned-messages-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Pinned-messages-iPhone-pseudo.png index eb9a82cea..890cdff78 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Pinned-messages-iPhone-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Pinned-messages-iPhone-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1aadd13737eb2768c20d5cd48fcb503120275ce73ef27ee3ec151ac5629be78c -size 1029398 +oid sha256:bc8677193d2595d5d6993d2e39c40b74fd033e02ac952da52dfc320824b857b0 +size 760909 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-decorator-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-decorator-iPad-en-GB.png index 44425394b..fe585f2db 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-decorator-iPad-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-decorator-iPad-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:89b62418f7c716f317fb3d0faee0ed5271cc99166740f517bb9082639465c618 -size 2574127 +oid sha256:8a3e77518c1d7225d0d89ac5d94f3a050721abb0320ac94c7768a64d9f8064c0 +size 1912810 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-decorator-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-decorator-iPad-pseudo.png index 82c2dd78c..040797316 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-decorator-iPad-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-decorator-iPad-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bb2a87744f36cd3317bfa0e79a8f0bc3f2a5077751b4da2e039c0c9edb23c149 -size 2585620 +oid sha256:648eb3d7089c0ce01bc4d20cddd0f0092421d78f2a0f483135b30f4d1bc5e665 +size 1919591 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-decorator-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-decorator-iPhone-en-GB.png index 2e40b5a7c..44cd4c30f 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-decorator-iPhone-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-decorator-iPhone-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a122378791c1f44057926bf054374e6f31eb3b4644438a7f3df988f62e8422aa -size 1154014 +oid sha256:6ec385fa56fdea70874f4ec43623561cba05f1b2c13633a46d560974e8487cb0 +size 865130 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-decorator-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-decorator-iPhone-pseudo.png index 9c4bed84f..ebb6ce399 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-decorator-iPhone-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-decorator-iPhone-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1752a98b3ddfa33cd8ee71fe2a49146b9a87d222fbad6fc41b4615d112948032 -size 1157827 +oid sha256:918a059719a7ff6f3974664904e9ce6473241a20907ebefda0c40561e6235823 +size 868790 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-summary-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-summary-iPad-en-GB.png index b4c02788d..ed38922f6 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-summary-iPad-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-summary-iPad-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:68bbaa372d7ef0f92176d530311c8484370aa38c7227a3a1f2dd7ed4d45240fc -size 2575915 +oid sha256:f1a2e2ed1e99b2ff6b92fe5ea4cf70af039ed09c542ede3f471e88f3d8883a2c +size 1916887 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-summary-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-summary-iPad-pseudo.png index 651181939..e29793abd 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-summary-iPad-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-summary-iPad-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b8cecf8be430f6efbe9451a6a1b4e6e2df8e3140e7a4fcea3c0a22e85e846d41 -size 2601396 +oid sha256:ad75f5eb536ef43505a9713b17d64424451b5f4136795ff72e6318f2cc74998b +size 1939145 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-summary-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-summary-iPhone-en-GB.png index a07ec9ee1..ec7919256 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-summary-iPhone-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-summary-iPhone-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0a86e9f58d2cde2339de39905762d92f57f8447896e16f24afa1adbd8329fd40 -size 1259715 +oid sha256:7f33ce5a5b646d0de6f7d57a86ec2cd4ffa2c0a88c99f0607b4388aff3ba7099 +size 944690 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-summary-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-summary-iPhone-pseudo.png index d038300cf..1d0ffa6cc 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-summary-iPhone-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Thread-summary-iPhone-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:09fb516ddcf8ab30b8ded663a15dec446cc932f9173ecdeb09a7b68d9d7515e6 -size 1259289 +oid sha256:44fb917f17f99ea73143c6f81c4a935e5f5d6de51ff6dccb61eb44e6a006b2f9 +size 945021 diff --git a/UnitTests/Sources/StaticLocationScreenViewModelTests.swift b/UnitTests/Sources/LocationSharingScreenViewModelTests.swift similarity index 74% rename from UnitTests/Sources/StaticLocationScreenViewModelTests.swift rename to UnitTests/Sources/LocationSharingScreenViewModelTests.swift index 1d47182c7..f4bbf59d6 100644 --- a/UnitTests/Sources/StaticLocationScreenViewModelTests.swift +++ b/UnitTests/Sources/LocationSharingScreenViewModelTests.swift @@ -11,20 +11,23 @@ import Combine import Testing @MainActor -struct StaticLocationScreenViewModelTests { +struct LocationSharingScreenViewModelTests { private let timelineProxy = TimelineProxyMock(.init()) - private var viewModel: StaticLocationScreenViewModelProtocol + private var viewModel: LocationSharingScreenViewModelProtocol - private var context: StaticLocationScreenViewModel.Context { + private var context: LocationSharingScreenViewModel.Context { viewModel.context } init() { - let viewModel = StaticLocationScreenViewModel(interactionMode: .picker, - mapURLBuilder: ServiceLocator.shared.settings.mapTilerConfiguration, - timelineController: MockTimelineController(timelineProxy: timelineProxy), - analytics: ServiceLocator.shared.analytics, - userIndicatorController: UserIndicatorControllerMock()) + let viewModel = LocationSharingScreenViewModel(interactionMode: .picker, + mapURLBuilder: ServiceLocator.shared.settings.mapTilerConfiguration, + liveLocationSharingEnabled: true, + roomProxy: JoinedRoomProxyMock(.init()), + timelineController: MockTimelineController(timelineProxy: timelineProxy), + analytics: ServiceLocator.shared.analytics, + userIndicatorController: UserIndicatorControllerMock(), + mediaProvider: MediaProviderMock(configuration: .init())) viewModel.state.bindings.isLocationAuthorized = true self.viewModel = viewModel } @@ -68,11 +71,11 @@ struct StaticLocationScreenViewModelTests { @Test func errorMapping() { let mapError = AlertInfo(locationSharingViewError: .mapError(.failedLoadingMap)) - #expect(mapError.message == L10n.errorFailedLoadingMap(InfoPlistReader.main.bundleDisplayName)) + #expect(mapError.title == L10n.errorFailedLoadingMap(InfoPlistReader.main.bundleDisplayName)) let locationError = AlertInfo(locationSharingViewError: .mapError(.failedLocatingUser)) - #expect(locationError.message == L10n.errorFailedLocatingUser(InfoPlistReader.main.bundleDisplayName)) + #expect(locationError.title == L10n.errorFailedLocatingUser(InfoPlistReader.main.bundleDisplayName)) let authorizationError = AlertInfo(locationSharingViewError: .missingAuthorization) - #expect(authorizationError.message == L10n.dialogPermissionLocationDescriptionIos) + #expect(authorizationError.message == L10n.dialogPermissionLocationDescriptionIos(InfoPlistReader.main.bundleDisplayName)) } @Test