From a2c1ca06dd83b1f2a0a4ab67631d89b675cdd79b Mon Sep 17 00:00:00 2001 From: Mauro <34335419+Velin92@users.noreply.github.com> Date: Mon, 6 Jul 2026 16:15:27 +0200 Subject: [PATCH] Content Scanning View (#5831) * implement a MediaView to delegate at the view level the usage of the content scanner service, to determine how to render the media. * fix for a small design mismatch * fix for greedy error state * updated naming * just making some screenshot that have been affected by a string change on Localazy * pr suggestions + updated strings --- .../Sources/GeneratedAccessibilityTests.swift | 4 + ElementX.xcodeproj/project.pbxproj | 12 ++ .../en-US.lproj/Localizable.strings | 15 +- .../en.lproj/Localizable.strings | 15 +- .../en.lproj/Localizable.stringsdict | 4 +- .../Localizations/en.lproj/SAS.strings | 2 +- .../en.lproj/Untranslated.strings | 4 +- ElementX/Sources/Generated/Strings.swift | 20 ++- .../Mocks/ContentScannerServiceMock.swift | 29 ++++ ElementX/Sources/Mocks/UserSessionMock.swift | 2 + .../SwiftUI/Views/ContentScanningView.swift | 130 ++++++++++++++++++ .../TestablePreviewsDictionary.swift | 1 + .../Screens/Timeline/TimelineViewModel.swift | 7 +- .../Style/TimelineItemBubbleBackground.swift | 14 +- .../Style/TimelineItemBubbledStylerView.swift | 48 ++++++- .../Style/TimelineItemSendInfoLabel.swift | 54 +++++--- .../TimelineReadReceiptsView.swift | 2 +- .../AudioRoomTimelineView.swift | 24 +++- .../ContentScanningFailureView.swift | 64 +++++++++ .../FileRoomTimelineView.swift | 63 +++++++-- .../ImageRoomTimelineView.swift | 69 +++++++--- .../StickerRoomTimelineView.swift | 77 +++++++---- .../VideoRoomTimelineView.swift | 48 +++++-- .../VoiceMessageRoomTimelineView.swift | 42 +++++- .../View/VoiceMessageRoomPlaybackView.swift | 6 +- .../Sources/GeneratedPreviewTests.swift | 7 + ...imelineView.Content-Scanner-iPad-en-GB.png | 3 + ...melineView.Content-Scanner-iPad-pseudo.png | 3 + ...elineView.Content-Scanner-iPhone-en-GB.png | 3 + ...lineView.Content-Scanner-iPhone-pseudo.png | 3 + ...ontentScanningFailureView.iPad-en-GB-0.png | 3 + ...ntentScanningFailureView.iPad-pseudo-0.png | 3 + ...tentScanningFailureView.iPhone-en-GB-0.png | 3 + ...entScanningFailureView.iPhone-pseudo-0.png | 3 + ...imelineView.Content-Scanner-iPad-en-GB.png | 3 + ...melineView.Content-Scanner-iPad-pseudo.png | 3 + ...elineView.Content-Scanner-iPhone-en-GB.png | 3 + ...lineView.Content-Scanner-iPhone-pseudo.png | 3 + ...imelineView.Content-Scanner-iPad-en-GB.png | 3 + ...melineView.Content-Scanner-iPad-pseudo.png | 3 + ...elineView.Content-Scanner-iPhone-en-GB.png | 3 + ...lineView.Content-Scanner-iPhone-pseudo.png | 3 + ...sEditScreen.Applying-change-iPad-en-GB.png | 4 +- ...EditScreen.Applying-change-iPad-pseudo.png | 4 +- ...ditScreen.Applying-change-iPhone-en-GB.png | 4 +- ...itScreen.Applying-change-iPhone-pseudo.png | 4 +- ...ingsEditScreen.Direct-Chats-iPad-en-GB.png | 4 +- ...ngsEditScreen.Direct-Chats-iPad-pseudo.png | 4 +- ...gsEditScreen.Direct-Chats-iPhone-en-GB.png | 4 +- ...sEditScreen.Direct-Chats-iPhone-pseudo.png | 4 +- ...up-Chats-Without-Disclaimer-iPad-en-GB.png | 4 +- ...p-Chats-Without-Disclaimer-iPad-pseudo.png | 4 +- ...-Chats-Without-Disclaimer-iPhone-en-GB.png | 4 +- ...Chats-Without-Disclaimer-iPhone-pseudo.png | 4 +- ...tingsEditScreen.Group-Chats-iPad-en-GB.png | 4 +- ...ingsEditScreen.Group-Chats-iPad-pseudo.png | 4 +- ...ngsEditScreen.Group-Chats-iPhone-en-GB.png | 4 +- ...gsEditScreen.Group-Chats-iPhone-pseudo.png | 4 +- ...CustomSectionView.Encrypted-iPad-en-GB.png | 4 +- ...ustomSectionView.Encrypted-iPad-pseudo.png | 4 +- ...stomSectionView.Encrypted-iPhone-en-GB.png | 4 +- ...tomSectionView.Encrypted-iPhone-pseudo.png | 4 +- ...stomSectionView.Unencrypted-iPad-en-GB.png | 4 +- ...tomSectionView.Unencrypted-iPad-pseudo.png | 4 +- ...omSectionView.Unencrypted-iPhone-en-GB.png | 4 +- ...mSectionView.Unencrypted-iPhone-pseudo.png | 4 +- ...UserDefinedScreen.Encrypted-iPad-en-GB.png | 4 +- ...serDefinedScreen.Encrypted-iPad-pseudo.png | 4 +- ...erDefinedScreen.Encrypted-iPhone-en-GB.png | 4 +- ...rDefinedScreen.Encrypted-iPhone-pseudo.png | 4 +- ...erDefinedScreen.Unencrypted-iPad-en-GB.png | 4 +- ...rDefinedScreen.Unencrypted-iPad-pseudo.png | 4 +- ...DefinedScreen.Unencrypted-iPhone-en-GB.png | 4 +- ...efinedScreen.Unencrypted-iPhone-pseudo.png | 4 +- .../searchScreen.Loaded-iPad-en-GB.png | 3 - .../searchScreen.Loaded-iPad-pseudo.png | 3 - .../searchScreen.Loaded-iPhone-en-GB.png | 3 - .../searchScreen.Loaded-iPhone-pseudo.png | 3 - ...imelineView.Content-Scanner-iPad-en-GB.png | 3 + ...melineView.Content-Scanner-iPad-pseudo.png | 3 + ...elineView.Content-Scanner-iPhone-en-GB.png | 3 + ...lineView.Content-Scanner-iPhone-pseudo.png | 3 + ...emBubbledStylerView.Replies-iPad-en-GB.png | 4 +- ...mBubbledStylerView.Replies-iPad-pseudo.png | 4 +- ...BubbledStylerView.Replies-iPhone-en-GB.png | 4 +- ...ubbledStylerView.Replies-iPhone-pseudo.png | 4 +- ...imelineView.Content-Scanner-iPad-en-GB.png | 3 + ...melineView.Content-Scanner-iPad-pseudo.png | 3 + ...elineView.Content-Scanner-iPhone-en-GB.png | 3 + ...lineView.Content-Scanner-iPhone-pseudo.png | 3 + ...imelineView.Content-Scanner-iPad-en-GB.png | 3 + ...melineView.Content-Scanner-iPad-pseudo.png | 3 + ...elineView.Content-Scanner-iPhone-en-GB.png | 3 + ...lineView.Content-Scanner-iPhone-pseudo.png | 3 + .../Sources/Compound/Icons/CompoundIcon.swift | 2 +- 95 files changed, 786 insertions(+), 219 deletions(-) create mode 100644 ElementX/Sources/Mocks/ContentScannerServiceMock.swift create mode 100644 ElementX/Sources/Other/SwiftUI/Views/ContentScanningView.swift create mode 100644 ElementX/Sources/Screens/Timeline/View/TimelineItemViews/ContentScanningFailureView.swift create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/audioRoomTimelineView.Content-Scanner-iPad-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/audioRoomTimelineView.Content-Scanner-iPad-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/audioRoomTimelineView.Content-Scanner-iPhone-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/audioRoomTimelineView.Content-Scanner-iPhone-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/contentScanningFailureView.iPad-en-GB-0.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/contentScanningFailureView.iPad-pseudo-0.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/contentScanningFailureView.iPhone-en-GB-0.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/contentScanningFailureView.iPhone-pseudo-0.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/fileRoomTimelineView.Content-Scanner-iPad-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/fileRoomTimelineView.Content-Scanner-iPad-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/fileRoomTimelineView.Content-Scanner-iPhone-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/fileRoomTimelineView.Content-Scanner-iPhone-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/imageRoomTimelineView.Content-Scanner-iPad-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/imageRoomTimelineView.Content-Scanner-iPad-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/imageRoomTimelineView.Content-Scanner-iPhone-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/imageRoomTimelineView.Content-Scanner-iPhone-pseudo.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/searchScreen.Loaded-iPad-en-GB.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/searchScreen.Loaded-iPad-pseudo.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/searchScreen.Loaded-iPhone-en-GB.png delete mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/searchScreen.Loaded-iPhone-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/stickerRoomTimelineView.Content-Scanner-iPad-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/stickerRoomTimelineView.Content-Scanner-iPad-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/stickerRoomTimelineView.Content-Scanner-iPhone-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/stickerRoomTimelineView.Content-Scanner-iPhone-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/videoRoomTimelineView.Content-Scanner-iPad-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/videoRoomTimelineView.Content-Scanner-iPad-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/videoRoomTimelineView.Content-Scanner-iPhone-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/videoRoomTimelineView.Content-Scanner-iPhone-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/voiceMessageRoomTimelineView.Content-Scanner-iPad-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/voiceMessageRoomTimelineView.Content-Scanner-iPad-pseudo.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/voiceMessageRoomTimelineView.Content-Scanner-iPhone-en-GB.png create mode 100644 PreviewTests/Sources/__Snapshots__/PreviewTests/voiceMessageRoomTimelineView.Content-Scanner-iPhone-pseudo.png diff --git a/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift b/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift index ae2774f42..8b312b4ac 100644 --- a/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift +++ b/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift @@ -99,6 +99,10 @@ extension AccessibilityTests { try await performAccessibilityAudit(named: "ComposerToolbar_Previews") } + func testContentScanningFailureView() async throws { + try await performAccessibilityAudit(named: "ContentScanningFailureView_Previews") + } + func testCopyTextButton() async throws { try await performAccessibilityAudit(named: "CopyTextButton_Previews") } diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 1250df809..ce067ab10 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -145,6 +145,7 @@ 15913A5B07118C1268A840E4 /* RoomSummaryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 046C0D3F53B0B5EF0A1F5BEA /* RoomSummaryTests.swift */; }; 1621BF6316FFFEF5AE067C77 /* Avatars.swift in Sources */ = {isa = PBXBuildFile; fileRef = C142248014E08E885E323E56 /* Avatars.swift */; }; 1653275750CE11F5CE94DDFD /* ReadReceiptsSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8063E65441E771200108C558 /* ReadReceiptsSummaryView.swift */; }; + 166D52EC67BD7586050BFB8D /* ContentScannerServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC446234C8E5031F36F1BBE2 /* ContentScannerServiceMock.swift */; }; 167D00CAA13FAFB822298021 /* MediaProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A81CCC2516D9CF9322DF01 /* MediaProviderTests.swift */; }; 167D5024DB9D44197AEA0507 /* HomeScreenNewSoundBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD5480F03306234FC086E93B /* HomeScreenNewSoundBanner.swift */; }; 16A1F6C703305FCAF4E14EC6 /* TimelineProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A8AA0DFA06012A9DAB951E /* TimelineProxyMock.swift */; }; @@ -630,6 +631,7 @@ 67FEA7157FA1B39DFC533C78 /* LeaveSpaceViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E5A65B5A000E7237AC61C67 /* LeaveSpaceViewModel.swift */; }; 6817EAD73DC1FFD8B943B5B9 /* HomeScreenRoomTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B73587C2E3CF5998361AE516 /* HomeScreenRoomTests.swift */; }; 68184EF36396424FE19A727D /* MediaLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFCE895ECFFA53FEE64D62B /* MediaLoader.swift */; }; + 681FA9A37E82A14EDFB22D56 /* ContentScanningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8794B3D86D7D444B9B6FF7B /* ContentScanningView.swift */; }; 6832733838C57A7D3FE8FEB5 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 50009897F60FAE7D63EF5E5B /* Kingfisher */; }; 6851B077B4C913CC12DB6E77 /* AppLockFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE93F0CBF0D96B77111C413 /* AppLockFlowCoordinator.swift */; }; 6885C3D26FC1026E07408D3C /* RoomListActivityVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5DA892E8643240C7BC41900 /* RoomListActivityVisibility.swift */; }; @@ -969,6 +971,7 @@ 9D79B94493FB32249F7E472F /* PlaceholderAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C705E605EF57C19DBE86FFA1 /* PlaceholderAvatarImage.swift */; }; 9D9690D2FD4CD26FF670620F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C75EF87651B00A176AB08E97 /* AppDelegate.swift */; }; 9DBF6524DFD8143A4D6A17F0 /* CreateRoomScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D432771B389EC486E7F90C6 /* CreateRoomScreenModels.swift */; }; + 9DD0DFFEFCA51AE241B637D1 /* ContentScanningFailureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A8442B74248E2839BA6FFF0 /* ContentScanningFailureView.swift */; }; 9DD5AA10E85137140FEA86A3 /* MediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17EFA1D3D09FC2F9C5E1CB2 /* MediaProvider.swift */; }; 9DD84E014ADFB2DD813022D5 /* RoomDetailsEditScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00E5B2CBEF8F96424F095508 /* RoomDetailsEditScreenViewModelTests.swift */; }; 9DE801D278AC34737467F937 /* VoiceMessageMediaManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 889DEDD63C68ABDA8AD29812 /* VoiceMessageMediaManagerProtocol.swift */; }; @@ -2174,6 +2177,7 @@ 5A37E2FACFD041CE466223CD /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 5A43964330459965AF048A8C /* LoginScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenViewModelTests.swift; sourceTree = ""; }; 5A70B03471F6027C90EE868C /* RoomMembersFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersFlowCoordinator.swift; sourceTree = ""; }; + 5A8442B74248E2839BA6FFF0 /* ContentScanningFailureView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentScanningFailureView.swift; sourceTree = ""; }; 5AEA0B743847CFA5B3C38EE4 /* RoomMembersListScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenCoordinator.swift; sourceTree = ""; }; 5B2A2B168C8DC14A1A9D1C64 /* NotificationToneManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationToneManagerProtocol.swift; sourceTree = ""; }; 5B8F0ED874DF8C9A51B0AB6F /* SettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenCoordinator.swift; sourceTree = ""; }; @@ -2799,6 +2803,7 @@ C7661EFFCAA307A97D71132A /* HomeScreenRoomList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomList.swift; sourceTree = ""; }; C79C2C6E7F66646D1D254927 /* RoomLiveLocationServiceMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomLiveLocationServiceMock.swift; sourceTree = ""; }; C830A64609CBD152F06E0457 /* NotificationConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationConstants.swift; sourceTree = ""; }; + C8794B3D86D7D444B9B6FF7B /* ContentScanningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentScanningView.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 = ""; }; @@ -2993,6 +2998,7 @@ EBD21AF0131AA38FF9534FAD /* EditRoomAddressScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditRoomAddressScreenModels.swift; sourceTree = ""; }; EBE5502760CF6CA2D7201883 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ja; path = ja.lproj/Localizable.stringsdict; sourceTree = ""; }; EBEB8D9F4940E161B18FE4BC /* UITestsNotificationCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsNotificationCenter.swift; sourceTree = ""; }; + EC446234C8E5031F36F1BBE2 /* ContentScannerServiceMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentScannerServiceMock.swift; sourceTree = ""; }; EC589E641AE46EFB2962534D /* RoomMemberDetailsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsViewModelTests.swift; sourceTree = ""; }; ECB836DD8BE31931F51B8AC9 /* EncryptionSettingsFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionSettingsFlowCoordinator.swift; sourceTree = ""; }; ECBBCD7444C2B515D02EC0E8 /* IdentityConfirmationScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreenViewModelTests.swift; sourceTree = ""; }; @@ -3839,6 +3845,7 @@ E2F96CCBEAAA7F2185BFA354 /* ClientProxyMock.swift */, C723327DC4A3093CD9675B27 /* CLLocationManagerMock.swift */, 4E600B315B920B9687F8EE1B /* ComposerDraftServiceMock.swift */, + EC446234C8E5031F36F1BBE2 /* ContentScannerServiceMock.swift */, 86E1BAA7232081635662A83F /* CXProviderMock.swift */, E321E840DCC63790049984F4 /* ElementCallServiceMock.swift */, 3A21027F05874B1BCC3E452B /* InvitedRoomProxyMock.swift */, @@ -3901,6 +3908,7 @@ C1FA515B3B0D61EF1E907D2D /* BadgeView.swift */, 07934EF08BB39353E4A94272 /* BlurEffectView.swift */, FEC4B431B0117BDEE697DB4A /* ComposerDisabledView.swift */, + C8794B3D86D7D444B9B6FF7B /* ContentScanningView.swift */, B682FE2C44C5E163E7023B05 /* CopyTextButton.swift */, 89D7846847BA9D2C5346ED9C /* EditAvatarButtonStyle.swift */, E2776E63E02719B20758EB78 /* EditRoomAddressListRow.swift */, @@ -6516,6 +6524,7 @@ 664ABD745A746C45CB842158 /* CallInviteRoomTimelineView.swift */, AD6E082B0507FB28F966516A /* CallNotificationRoomTimelineView.swift */, BFBF273BC2BFB9F3EEFA988B /* CollapsibleRoomTimelineView.swift */, + 5A8442B74248E2839BA6FFF0 /* ContentScanningFailureView.swift */, 44ABA63DBE7F76C58260B43B /* EmoteRoomTimelineView.swift */, 2F06F70B9C433BAD4BC6B9F5 /* EncryptedRoomTimelineView.swift */, 4F75EF13F49DD2204E760910 /* FileRoomTimelineView.swift */, @@ -8382,7 +8391,10 @@ 9ED768A43A2A302F9812C5DF /* ContentScannerProxy.swift in Sources */, 32BFB9B4D2EC46196537B72F /* ContentScannerProxyProtocol.swift in Sources */, E52F1E00FBAABA703DA510F9 /* ContentScannerService.swift in Sources */, + 166D52EC67BD7586050BFB8D /* ContentScannerServiceMock.swift in Sources */, E8F88136C8E69D9E69621E19 /* ContentScannerServiceProtocol.swift in Sources */, + 9DD0DFFEFCA51AE241B637D1 /* ContentScanningFailureView.swift in Sources */, + 681FA9A37E82A14EDFB22D56 /* ContentScanningView.swift in Sources */, 73DBE886625AF56FF08D7F76 /* CoordinateAnimator.swift in Sources */, C3522917C0C367C403429EEC /* CoordinatorProtocol.swift in Sources */, 633501761094E09DFBEBFFAD /* CopyTextButton.swift in Sources */, diff --git a/ElementX/Resources/Localizations/en-US.lproj/Localizable.strings b/ElementX/Resources/Localizations/en-US.lproj/Localizable.strings index f7b28330a..654152fe6 100644 --- a/ElementX/Resources/Localizations/en-US.lproj/Localizable.strings +++ b/ElementX/Resources/Localizations/en-US.lproj/Localizable.strings @@ -2,6 +2,7 @@ "a11y_add_reaction" = "Add reaction: %1$@"; "a11y_address" = "Address"; "a11y_avatar" = "Avatar"; +"a11y_clear_search_field" = "Clear search field"; "a11y_collapse_message_text_field" = "Minimize message text field"; "a11y_create_poll_votes_allowed_decrease" = "Decrease votes allowed per person"; "a11y_create_poll_votes_allowed_increase" = "Increase votes allowed per person"; @@ -36,9 +37,9 @@ "a11y_qr_code" = "QR Code"; "a11y_react_with" = "React with %1$@"; "a11y_react_with_other_emojis" = "React with other emojis"; -"a11y_read_receipts_multiple" = "Read by %1$@ and %2$@"; -"a11y_read_receipts_single" = "Read by %1$@"; -"a11y_read_receipts_tap_to_show_all" = "Tap to show all"; +"a11y_read_receipts_multiple" = "Previous message read by %1$@ and %2$@"; +"a11y_read_receipts_single" = "Previous message read by %1$@"; +"a11y_read_receipts_tap_to_show_all_ios" = "Tap to show all"; "a11y_remove_reaction" = "Remove reaction: %1$@"; "a11y_remove_reaction_with" = "Remove reaction with %1$@"; "a11y_send_files" = "Send files"; @@ -408,6 +409,10 @@ "confirm_recovery_key_banner_primary_button_title" = "Enter your recovery key"; "confirm_recovery_key_banner_secondary_button_title" = "Forgot your recovery key?"; "confirm_recovery_key_banner_title" = "Your key storage is out of sync"; +"content_scanner_not_found" = "Download not available."; +"content_scanner_not_found_title" = "File not found"; +"content_scanner_unsafe_message" = "Preview and download have been disabled."; +"content_scanner_unsafe_title" = "The file is not safe"; "crash_detection_dialog_content" = "%1$@ crashed the last time it was used. Would you like to share a crash report with us?"; "crypto_event_authenticity_mismatched_sender" = "The sender of the event does not match the owner of the device that sent it."; "crypto_event_authenticity_not_guaranteed" = "The authenticity of this encrypted message can't be guaranteed on this device."; @@ -1030,7 +1035,7 @@ "screen_notification_settings_edit_custom_settings_section_title" = "Custom setting per chat"; "screen_notification_settings_edit_failed_updating_default_mode" = "An error occurred while updating the notification setting."; "screen_notification_settings_edit_mode_all_messages" = "All messages"; -"screen_notification_settings_edit_mode_mentions_and_keywords" = "Mentions and Keywords only"; +"screen_notification_settings_edit_mode_mentions_and_keywords" = "Mentions and replies only"; "screen_notification_settings_edit_screen_direct_section_header" = "On direct chats, notify me for"; "screen_notification_settings_edit_screen_group_section_header" = "On group chats, notify me for"; "screen_notification_settings_enable_notifications" = "Enable notifications on this device"; @@ -1336,7 +1341,7 @@ "screen_room_notification_settings_error_setting_mode" = "Failed setting the mode, please try again."; "screen_room_notification_settings_mentions_only_disclaimer" = "Your homeserver does not support this option in encrypted rooms, you won't get notified in this room."; "screen_room_notification_settings_mode_all_messages" = "All messages"; -"screen_room_notification_settings_mode_mentions_and_keywords" = "Mentions and Keywords only"; +"screen_room_notification_settings_mode_mentions_and_keywords" = "Mentions and replies only"; "screen_room_notification_settings_room_custom_settings_title" = "In this room, notify me for"; "screen_room_pinned_banner_indicator" = "%1$@ of %2$@"; "screen_room_pinned_banner_indicator_description" = "%1$@ Pinned messages"; diff --git a/ElementX/Resources/Localizations/en.lproj/Localizable.strings b/ElementX/Resources/Localizations/en.lproj/Localizable.strings index 97b8a09ca..d40511bec 100644 --- a/ElementX/Resources/Localizations/en.lproj/Localizable.strings +++ b/ElementX/Resources/Localizations/en.lproj/Localizable.strings @@ -2,6 +2,7 @@ "a11y_add_reaction" = "Add reaction: %1$@"; "a11y_address" = "Address"; "a11y_avatar" = "Avatar"; +"a11y_clear_search_field" = "Clear search field"; "a11y_collapse_message_text_field" = "Minimise message text field"; "a11y_create_poll_votes_allowed_decrease" = "Decrease votes allowed per person"; "a11y_create_poll_votes_allowed_increase" = "Increase votes allowed per person"; @@ -36,9 +37,9 @@ "a11y_qr_code" = "QR Code"; "a11y_react_with" = "React with %1$@"; "a11y_react_with_other_emojis" = "React with other emojis"; -"a11y_read_receipts_multiple" = "Read by %1$@ and %2$@"; -"a11y_read_receipts_single" = "Read by %1$@"; -"a11y_read_receipts_tap_to_show_all" = "Tap to show all"; +"a11y_read_receipts_multiple" = "Previous message read by %1$@ and %2$@"; +"a11y_read_receipts_single" = "Previous message read by %1$@"; +"a11y_read_receipts_tap_to_show_all_ios" = "Tap to show all"; "a11y_remove_reaction" = "Remove reaction: %1$@"; "a11y_remove_reaction_with" = "Remove reaction with %1$@"; "a11y_send_files" = "Send files"; @@ -408,6 +409,10 @@ "confirm_recovery_key_banner_primary_button_title" = "Enter your recovery key"; "confirm_recovery_key_banner_secondary_button_title" = "Forgot your recovery key?"; "confirm_recovery_key_banner_title" = "Your key storage is out of sync"; +"content_scanner_not_found" = "Download not available."; +"content_scanner_not_found_title" = "File not found"; +"content_scanner_unsafe_message" = "Preview and download have been disabled."; +"content_scanner_unsafe_title" = "The file is not safe"; "crash_detection_dialog_content" = "%1$@ crashed the last time it was used. Would you like to share a crash report with us?"; "crypto_event_authenticity_mismatched_sender" = "The sender of the event does not match the owner of the device that sent it."; "crypto_event_authenticity_not_guaranteed" = "The authenticity of this encrypted message can't be guaranteed on this device."; @@ -1030,7 +1035,7 @@ "screen_notification_settings_edit_custom_settings_section_title" = "Custom setting per chat"; "screen_notification_settings_edit_failed_updating_default_mode" = "An error occurred while updating the notification setting."; "screen_notification_settings_edit_mode_all_messages" = "All messages"; -"screen_notification_settings_edit_mode_mentions_and_keywords" = "Mentions and Keywords only"; +"screen_notification_settings_edit_mode_mentions_and_keywords" = "Mentions and replies only"; "screen_notification_settings_edit_screen_direct_section_header" = "On direct chats, notify me for"; "screen_notification_settings_edit_screen_group_section_header" = "On group chats, notify me for"; "screen_notification_settings_enable_notifications" = "Enable notifications on this device"; @@ -1336,7 +1341,7 @@ "screen_room_notification_settings_error_setting_mode" = "Failed setting the mode, please try again."; "screen_room_notification_settings_mentions_only_disclaimer" = "Your homeserver does not support this option in encrypted rooms, you won't get notified in this room."; "screen_room_notification_settings_mode_all_messages" = "All messages"; -"screen_room_notification_settings_mode_mentions_and_keywords" = "Mentions and Keywords only"; +"screen_room_notification_settings_mode_mentions_and_keywords" = "Mentions and replies only"; "screen_room_notification_settings_room_custom_settings_title" = "In this room, notify me for"; "screen_room_pinned_banner_indicator" = "%1$@ of %2$@"; "screen_room_pinned_banner_indicator_description" = "%1$@ Pinned messages"; diff --git a/ElementX/Resources/Localizations/en.lproj/Localizable.stringsdict b/ElementX/Resources/Localizations/en.lproj/Localizable.stringsdict index 3c2c9a6e1..f42e12a8d 100644 --- a/ElementX/Resources/Localizations/en.lproj/Localizable.stringsdict +++ b/ElementX/Resources/Localizations/en.lproj/Localizable.stringsdict @@ -43,9 +43,9 @@ NSStringFormatValueTypeKey d one - Read by %1$@ and %2$d other + Previous message read by %1$@ and %2$d other other - Read by %1$@ and %2$d others + Previous message read by %1$@ and %2$d others NSStringLocalizedFormatKey %#@COUNT@ diff --git a/ElementX/Resources/Localizations/en.lproj/SAS.strings b/ElementX/Resources/Localizations/en.lproj/SAS.strings index d29ccc232..dcd93cf93 100644 --- a/ElementX/Resources/Localizations/en.lproj/SAS.strings +++ b/ElementX/Resources/Localizations/en.lproj/SAS.strings @@ -61,4 +61,4 @@ "trumpet" = "Trumpet"; "turtle" = "Turtle"; "umbrella" = "Umbrella"; -"unicorn" = "Unicorn"; \ No newline at end of file +"unicorn" = "Unicorn"; diff --git a/ElementX/Resources/Localizations/en.lproj/Untranslated.strings b/ElementX/Resources/Localizations/en.lproj/Untranslated.strings index 8ee776e7f..a1e5ae2e2 100644 --- a/ElementX/Resources/Localizations/en.lproj/Untranslated.strings +++ b/ElementX/Resources/Localizations/en.lproj/Untranslated.strings @@ -1,9 +1,9 @@ "screen_home_tab_search" = "Search"; "screen_search_empty_state_message" = "Search for chats and messages"; -"screen_search_tab_messages" = "Messages"; -"screen_search_tab_rooms" = "Rooms"; "screen_search_empty_state_title" = "Start searching..."; "screen_search_no_results_message" = "There are no results for “%1$@.” Try a new search term."; +"screen_search_tab_messages" = "Messages"; +"screen_search_tab_rooms" = "Rooms"; "soft_logout_clear_data_dialog_content" = "Clear all data currently stored on this device?\nSign in again to access your account data and messages."; "soft_logout_clear_data_dialog_title" = "Clear data"; "soft_logout_clear_data_notice" = "Warning: Your personal data (including encryption keys) is still stored on this device.\n\nClear it if you’re finished using this device, or want to sign in to another account."; diff --git a/ElementX/Sources/Generated/Strings.swift b/ElementX/Sources/Generated/Strings.swift index 87ca2cffe..2f530bf62 100644 --- a/ElementX/Sources/Generated/Strings.swift +++ b/ElementX/Sources/Generated/Strings.swift @@ -20,6 +20,8 @@ internal nonisolated enum L10n { internal static var a11yAddress: String { return L10n.tr("Localizable", "a11y_address") } /// Avatar internal static var a11yAvatar: String { return L10n.tr("Localizable", "a11y_avatar") } + /// Clear search field + internal static var a11yClearSearchField: String { return L10n.tr("Localizable", "a11y_clear_search_field") } /// Minimise message text field internal static var a11yCollapseMessageTextField: String { return L10n.tr("Localizable", "a11y_collapse_message_text_field") } /// Decrease votes allowed per person @@ -108,7 +110,7 @@ internal nonisolated enum L10n { } /// React with other emojis internal static var a11yReactWithOtherEmojis: String { return L10n.tr("Localizable", "a11y_react_with_other_emojis") } - /// Read by %1$@ and %2$@ + /// Previous message read by %1$@ and %2$@ internal static func a11yReadReceiptsMultiple(_ p1: Any, _ p2: Any) -> String { return L10n.tr("Localizable", "a11y_read_receipts_multiple", String(describing: p1), String(describing: p2)) } @@ -116,12 +118,12 @@ internal nonisolated enum L10n { internal static func a11yReadReceiptsMultipleWithOthers(_ p1: Int) -> String { return L10n.tr("Localizable", "a11y_read_receipts_multiple_with_others", p1) } - /// Read by %1$@ + /// Previous message read by %1$@ internal static func a11yReadReceiptsSingle(_ p1: Any) -> String { return L10n.tr("Localizable", "a11y_read_receipts_single", String(describing: p1)) } /// Tap to show all - internal static var a11yReadReceiptsTapToShowAll: String { return L10n.tr("Localizable", "a11y_read_receipts_tap_to_show_all") } + internal static var a11yReadReceiptsTapToShowAllIos: String { return L10n.tr("Localizable", "a11y_read_receipts_tap_to_show_all_ios") } /// Remove reaction: %1$@ internal static func a11yRemoveReaction(_ p1: Any) -> String { return L10n.tr("Localizable", "a11y_remove_reaction", String(describing: p1)) @@ -900,6 +902,14 @@ internal nonisolated enum L10n { internal static var confirmRecoveryKeyBannerSecondaryButtonTitle: String { return L10n.tr("Localizable", "confirm_recovery_key_banner_secondary_button_title") } /// Your key storage is out of sync internal static var confirmRecoveryKeyBannerTitle: String { return L10n.tr("Localizable", "confirm_recovery_key_banner_title") } + /// Download not available. + internal static var contentScannerNotFound: String { return L10n.tr("Localizable", "content_scanner_not_found") } + /// File not found + internal static var contentScannerNotFoundTitle: String { return L10n.tr("Localizable", "content_scanner_not_found_title") } + /// Preview and download have been disabled. + internal static var contentScannerUnsafeMessage: String { return L10n.tr("Localizable", "content_scanner_unsafe_message") } + /// The file is not safe + internal static var contentScannerUnsafeTitle: String { return L10n.tr("Localizable", "content_scanner_unsafe_title") } /// %1$@ crashed the last time it was used. Would you like to share a crash report with us? internal static func crashDetectionDialogContent(_ p1: Any) -> String { return L10n.tr("Localizable", "crash_detection_dialog_content", String(describing: p1)) @@ -2396,7 +2406,7 @@ internal nonisolated enum L10n { internal static var screenNotificationSettingsEditFailedUpdatingDefaultMode: String { return L10n.tr("Localizable", "screen_notification_settings_edit_failed_updating_default_mode") } /// All messages internal static var screenNotificationSettingsEditModeAllMessages: String { return L10n.tr("Localizable", "screen_notification_settings_edit_mode_all_messages") } - /// Mentions and Keywords only + /// Mentions and replies only internal static var screenNotificationSettingsEditModeMentionsAndKeywords: String { return L10n.tr("Localizable", "screen_notification_settings_edit_mode_mentions_and_keywords") } /// On direct chats, notify me for internal static var screenNotificationSettingsEditScreenDirectSectionHeader: String { return L10n.tr("Localizable", "screen_notification_settings_edit_screen_direct_section_header") } @@ -3080,7 +3090,7 @@ internal nonisolated enum L10n { internal static var screenRoomNotificationSettingsMentionsOnlyDisclaimer: String { return L10n.tr("Localizable", "screen_room_notification_settings_mentions_only_disclaimer") } /// All messages internal static var screenRoomNotificationSettingsModeAllMessages: String { return L10n.tr("Localizable", "screen_room_notification_settings_mode_all_messages") } - /// Mentions and Keywords only + /// Mentions and replies only internal static var screenRoomNotificationSettingsModeMentionsAndKeywords: String { return L10n.tr("Localizable", "screen_room_notification_settings_mode_mentions_and_keywords") } /// In this room, notify me for internal static var screenRoomNotificationSettingsRoomCustomSettingsTitle: String { return L10n.tr("Localizable", "screen_room_notification_settings_room_custom_settings_title") } diff --git a/ElementX/Sources/Mocks/ContentScannerServiceMock.swift b/ElementX/Sources/Mocks/ContentScannerServiceMock.swift new file mode 100644 index 000000000..066338983 --- /dev/null +++ b/ElementX/Sources/Mocks/ContentScannerServiceMock.swift @@ -0,0 +1,29 @@ +// +// 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 Foundation + +struct ContentScannerServiceMockConfiguration { + /// The verdict reported for every source: `true` when safe, `false` when unsafe, or `nil` to + /// never resolve the scan so that the scanning state remains visible. + var scanResult: Bool? +} + +extension ContentScannerServiceMock { + convenience init(_ configuration: ContentScannerServiceMockConfiguration) { + self.init() + + scanResultFromSourceReturnValue = configuration.scanResult + loadScanResultFromSourceClosure = { _ in + guard let scanResult = configuration.scanResult else { + try? await Task.sleep(for: .seconds(3600)) + return .failure(.failedScanning) + } + return .success(scanResult) + } + } +} diff --git a/ElementX/Sources/Mocks/UserSessionMock.swift b/ElementX/Sources/Mocks/UserSessionMock.swift index 721242314..2533fc1af 100644 --- a/ElementX/Sources/Mocks/UserSessionMock.swift +++ b/ElementX/Sources/Mocks/UserSessionMock.swift @@ -11,6 +11,7 @@ import Foundation struct UserSessionMockConfiguration { var clientProxy: ClientProxyProtocol = ClientProxyMock(.init()) + var contentScannerService: ContentScannerServiceProtocol? } @MainActor extension UserSessionMock { @@ -20,6 +21,7 @@ struct UserSessionMockConfiguration { clientProxy = configuration.clientProxy mediaProvider = MediaProviderMock(.init()) voiceMessageMediaManager = VoiceMessageMediaManagerMock() + contentScannerService = configuration.contentScannerService sessionSecurityStatePublisher = CurrentValueSubject(.init(verificationState: .verified, recoveryState: .enabled)).asCurrentValuePublisher() diff --git a/ElementX/Sources/Other/SwiftUI/Views/ContentScanningView.swift b/ElementX/Sources/Other/SwiftUI/Views/ContentScanningView.swift new file mode 100644 index 000000000..990f8de84 --- /dev/null +++ b/ElementX/Sources/Other/SwiftUI/Views/ContentScanningView.swift @@ -0,0 +1,130 @@ +// +// 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 + +/// The reason a media item can't be presented. +nonisolated enum ContentScanningFailure { + /// The content scanner flagged the media as unsafe. + case notSafe + /// The media couldn't be scanned, e.g. it wasn't found or a network error occurred. + case notFound +} + +/// Reports a ``ContentScanningFailure`` up the view hierarchy so that ancestors (such as the +/// message bubble) can adopt the critical styling without having to track the scan state themselves. +nonisolated struct ContentScanningFailurePreferenceKey: PreferenceKey { + static let defaultValue: ContentScanningFailure? = nil + + static func reduce(value: inout ContentScanningFailure?, nextValue: () -> ContentScanningFailure?) { + value = value ?? nextValue() + } +} + +/// A wrapper that gates the presentation of a media item on its content scan state. +/// +/// The safe content is rendered when the media is known to be safe or when there's no content +/// scanner configured, the scanning content whilst a scan is in flight and the unsafe content +/// when the media is unsafe or couldn't be scanned. Any failure is also reported up the view +/// hierarchy through ``ContentScanningFailurePreferenceKey``. +/// +/// - Important: Any interactions with the media (taps, playback etc.) must live inside the safe +/// content so that they're unavailable whilst the media is being scanned or is unsafe. +struct ContentScanningView: View { + private enum ScanState { + case safe + case scanning + case unsafe(ContentScanningFailure) + } + + private let contentScannerService: ContentScannerServiceProtocol? + private let mediaSource: MediaSourceProxy? + private let safeContent: () -> SafeContent + private let scanningContent: () -> ScanningContent + private let unsafeContent: (ContentScanningFailure) -> UnsafeContent + + @State private var scanState: ScanState? + + /// - Parameters: + /// - contentScannerService: The service used to scan the media. Passing `nil` disables + /// scanning and the content is always rendered. + /// - mediaSource: The media source to scan. Passing `nil` renders the content, this + /// makes it convenient to wrap items whose media source is optional. + /// - safeContent: The regular presentation of the media, including any interactions with it. + /// - scanningContent: The presentation of the media whilst it's being scanned. + /// - unsafeContent: The presentation of the media when it can't be shown. + init(contentScannerService: ContentScannerServiceProtocol?, + mediaSource: MediaSourceProxy?, + @ViewBuilder safeContent: @escaping () -> SafeContent, + @ViewBuilder scanningContent: @escaping () -> ScanningContent, + @ViewBuilder unsafeContent: @escaping (ContentScanningFailure) -> UnsafeContent) { + self.contentScannerService = contentScannerService + self.mediaSource = mediaSource + self.safeContent = safeContent + self.scanningContent = scanningContent + self.unsafeContent = unsafeContent + } + + var body: some View { + content + .preference(key: ContentScanningFailurePreferenceKey.self, value: scanFailure) + .task(id: mediaSource?.url.absoluteString) { + await scan() + } + } + + @ViewBuilder + private var content: some View { + switch resolvedScanState { + case .safe: + safeContent() + case .scanning: + scanningContent() + case .unsafe(let failure): + unsafeContent(failure) + } + } + + private var scanFailure: ContentScanningFailure? { + guard case .unsafe(let failure) = resolvedScanState else { + return nil + } + return failure + } + + private var resolvedScanState: ScanState { + guard let contentScannerService, let mediaSource else { + return .safe + } + + if let scanState { + return scanState + } + + // Until the scan triggered by this view resolves, reflect any verdict already in the cache. + switch contentScannerService.scanResultFromSource(mediaSource) { + case true: return .safe + case false: return .unsafe(.notSafe) + default: return .scanning + } + } + + private func scan() async { + guard let contentScannerService, let mediaSource else { + return + } + + switch await contentScannerService.loadScanResultFromSource(mediaSource) { + case .success(true): + scanState = .safe + case .success(false): + scanState = .unsafe(.notSafe) + case .failure: + scanState = .unsafe(.notFound) + } + } +} diff --git a/ElementX/Sources/Other/TestablePreview/TestablePreviewsDictionary.swift b/ElementX/Sources/Other/TestablePreview/TestablePreviewsDictionary.swift index 86c0695c9..30e0d4f38 100644 --- a/ElementX/Sources/Other/TestablePreview/TestablePreviewsDictionary.swift +++ b/ElementX/Sources/Other/TestablePreview/TestablePreviewsDictionary.swift @@ -33,6 +33,7 @@ enum TestablePreviewsDictionary { "CollapsibleRoomTimelineView_Previews" : CollapsibleRoomTimelineView_Previews.self, "CompletionSuggestion_Previews" : CompletionSuggestion_Previews.self, "ComposerToolbar_Previews" : ComposerToolbar_Previews.self, + "ContentScanningFailureView_Previews" : ContentScanningFailureView_Previews.self, "CopyTextButton_Previews" : CopyTextButton_Previews.self, "CreateRoomSpaceSelectionSheet_Previews" : CreateRoomSpaceSelectionSheet_Previews.self, "CreateRoom_Previews" : CreateRoom_Previews.self, diff --git a/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift b/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift index 8961bff9b..ea60787d1 100644 --- a/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift +++ b/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift @@ -1116,7 +1116,10 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol { extension TimelineViewModel { static let mock = mock(timelineKind: .live) - static func mock(timelineKind: TimelineKind = .live, timelineController: TimelineControllerMock? = nil, hasPredecessor: Bool = false) -> TimelineViewModel { + static func mock(timelineKind: TimelineKind = .live, + timelineController: TimelineControllerMock? = nil, + hasPredecessor: Bool = false, + contentScannerService: ContentScannerServiceProtocol? = nil) -> TimelineViewModel { let clientProxyMock = ClientProxyMock(.init()) clientProxyMock.roomSummaryForAliasReturnValue = .mock(id: "!room:matrix.org", name: "Room") clientProxyMock.roomSummaryForIdentifierReturnValue = .mock(id: "!room:matrix.org", name: "Room", canonicalAlias: "#room:matrix.org") @@ -1127,7 +1130,7 @@ extension TimelineViewModel { return TimelineViewModel(roomProxy: roomProxy, focussedEventID: nil, timelineController: timelineController ?? TimelineControllerMock(.init(timelineKind: timelineKind)), - userSession: UserSessionMock(.init(clientProxy: clientProxyMock)), + userSession: UserSessionMock(.init(clientProxy: clientProxyMock, contentScannerService: contentScannerService)), mediaPlayerProvider: MediaPlayerProviderMock(), userIndicatorController: UserIndicatorControllerMock(), appMediator: AppMediatorMock(.init()), diff --git a/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbleBackground.swift b/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbleBackground.swift index cdcf88d4f..329dddb91 100644 --- a/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbleBackground.swift +++ b/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbleBackground.swift @@ -14,12 +14,15 @@ extension View { /// - isOutgoing: rounds the corners according to the side it shows on, defaults to true /// - insets: defaults to what we use for file timeline items, text uses custom values /// - color: self explanatory, defaults to subtle secondary + /// - borderColor: an optional colour for a border around the bubble func bubbleBackground(isOutgoing: Bool = true, insets: EdgeInsets = .init(top: 8, leading: 12, bottom: 8, trailing: 12), - color: @autoclosure @MainActor () -> Color? = .compound.bgSubtleSecondary) -> some View { + color: @autoclosure @MainActor () -> Color? = .compound.bgSubtleSecondary, + borderColor: @autoclosure @MainActor () -> Color? = nil) -> some View { modifier(TimelineItemBubbleBackgroundModifier(isOutgoing: isOutgoing, insets: insets, - color: color())) + color: color(), + borderColor: borderColor())) } } @@ -29,12 +32,19 @@ private struct TimelineItemBubbleBackgroundModifier: ViewModifier { let isOutgoing: Bool let insets: EdgeInsets var color: Color? + var borderColor: Color? func body(content: Content) -> some View { content .padding(insets) .background(color) .cornerRadius(12, corners: roundedCorners) + .overlay { + if let borderColor { + RoundedCornerShape(radius: 12, corners: roundedCorners) + .stroke(borderColor) + } + } } private var roundedCorners: UIRectCorner { diff --git a/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbledStylerView.swift b/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbledStylerView.swift index 87328c894..9c44b509b 100644 --- a/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbledStylerView.swift +++ b/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbledStylerView.swift @@ -18,6 +18,14 @@ struct TimelineItemBubbledStylerView: View { let adjustedDeliveryStatus: TimelineItemDeliveryStatus? @ViewBuilder let content: () -> Content + /// Whether the item's media failed content scanning, in which case the whole bubble adopts + /// the critical styling. Reported by the item's `ContentScanningView` through the preference key. + @State private var contentScanningFailure: ContentScanningFailure? + + private var hasContentScanningFailure: Bool { + contentScanningFailure != nil + } + private var isDM: Bool { context.viewState.isDM } @@ -78,6 +86,7 @@ struct TimelineItemBubbledStylerView: View { } .padding(EdgeInsets(top: 1, leading: 8, bottom: 1, trailing: 8)) .highlightedTimelineItem(isFocussed) + .onPreferenceChange(ContentScanningFailurePreferenceKey.self) { contentScanningFailure = $0 } } @ViewBuilder @@ -179,10 +188,14 @@ struct TimelineItemBubbledStylerView: View { var messageBubble: some View { contentWithReply - .timelineItemSendInfo(timelineItem: timelineItem, adjustedDeliveryStatus: adjustedDeliveryStatus, context: context) + .timelineItemSendInfo(timelineItem: timelineItem, + adjustedDeliveryStatus: adjustedDeliveryStatus, + hasContentScanningFailure: hasContentScanningFailure, + context: context) .bubbleBackground(isOutgoing: timelineItem.isOutgoing, - insets: timelineItem.bubbleInsets, - color: timelineItem.bubbleBackgroundColor) + insets: timelineItem.bubbleInsets(hasContentScanningFailure: hasContentScanningFailure), + color: hasContentScanningFailure ? .compound.bgCriticalSubtle : timelineItem.bubbleBackgroundColor, + borderColor: hasContentScanningFailure ? .compound.borderCriticalSubtle : nil) } var contentWithReply: some View { @@ -248,9 +261,14 @@ private extension EventBasedTimelineItemProtocol { /// The insets for the full bubble content. /// Padding affecting just the "send info" should be added inside `TimelineItemSendInfoView` - var bubbleInsets: EdgeInsets { + func bubbleInsets(hasContentScanningFailure: Bool) -> EdgeInsets { let defaultInsets: EdgeInsets = .init(around: 8) + // The content scanner failure placeholder is always rendered inset within the critical bubble. + if hasContentScanningFailure { + return defaultInsets + } + switch self { case is StickerRoomTimelineItem: return .zero @@ -370,6 +388,8 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview timelineControllerFactory: TimelineControllerFactoryMock(.init())) }() + static let unsafeViewModel = TimelineViewModel.mock(contentScannerService: ContentScannerServiceMock(.init(scanResult: false))) + static var previews: some View { mockTimeline .previewDisplayName("Mock Timeline") @@ -439,6 +459,26 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview content: .init(body: "Short message"), properties: properties), groupStyle: .single)) + + RoomTimelineItemView(viewState: .init(item: FileRoomTimelineItem(id: .randomEvent, + timestamp: .mock, + isOutgoing: true, + isEditable: false, + canBeRepliedTo: true, + sender: .init(id: "whoever"), + content: .init(filename: "unsafe.pdf", + caption: "Replying with an unsafe file.", + formattedCaption: nil, + source: try? MediaSourceProxy(url: .mockMXCFile, mimeType: nil), + fileSize: 3 * 1024 * 1024, + thumbnailSource: nil, + contentType: nil), + properties: .init(replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"), + eventID: "123", + eventContent: .message(.text(.init(body: "Short")))))), + groupStyle: .single)) + .environmentObject(unsafeViewModel.context) + .environment(\.timelineContext, unsafeViewModel.context) } .environmentObject(viewModel.context) .environment(\.timelineContext, viewModel.context) diff --git a/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemSendInfoLabel.swift b/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemSendInfoLabel.swift index 5372a09d1..5a28df167 100644 --- a/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemSendInfoLabel.swift +++ b/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemSendInfoLabel.swift @@ -13,9 +13,11 @@ extension View { /// Adds the send info (timestamp along indicators for edits and delivery/encryption issues) for the given timeline item to this view. func timelineItemSendInfo(timelineItem: EventBasedTimelineItemProtocol, adjustedDeliveryStatus: TimelineItemDeliveryStatus?, + hasContentScanningFailure: Bool = false, context: TimelineViewModel.Context) -> some View { modifier(TimelineItemSendInfoModifier(sendInfo: .init(timelineItem: timelineItem, - adjustedDeliveryStatus: adjustedDeliveryStatus), + adjustedDeliveryStatus: adjustedDeliveryStatus, + hasContentScanningFailure: hasContentScanningFailure), context: context)) } } @@ -148,7 +150,9 @@ private struct TimelineItemSendInfo { } private extension TimelineItemSendInfo { - init(timelineItem: EventBasedTimelineItemProtocol, adjustedDeliveryStatus: TimelineItemDeliveryStatus?) { + init(timelineItem: EventBasedTimelineItemProtocol, + adjustedDeliveryStatus: TimelineItemDeliveryStatus?, + hasContentScanningFailure: Bool = false) { itemID = timelineItem.id localizedString = timelineItem.localizedSendInfo @@ -162,29 +166,35 @@ private extension TimelineItemSendInfo { nil } - layoutType = switch timelineItem { - case is TextBasedRoomTimelineItem: - .overlay(capsuleStyle: false) - case let liveLocationTimelineItem as LiveLocationRoomTimelineItem: - liveLocationTimelineItem.layout - case let message as EventBasedMessageTimelineItemProtocol: - switch message { - case is ImageRoomTimelineItem, is VideoRoomTimelineItem: - .overlay(capsuleStyle: !message.hasMediaCaption) - case is AudioRoomTimelineItem, is FileRoomTimelineItem: - // swiftlint:disable:next void_function_in_ternary - message.hasMediaCaption ? .overlay(capsuleStyle: false) : .horizontal(spacing: 0) // No spacing as the content already contains it. - case let locationTimelineItem as LocationRoomTimelineItem: - .overlay(capsuleStyle: locationTimelineItem.content.geoURI != nil) + layoutType = if hasContentScanningFailure { + // The content scanner failure placeholder replaces the media, + // so the send info is laid out like it is for a text message. + timelineItem.hasMediaCaption ? .overlay(capsuleStyle: false) : .horizontal() + } else { + switch timelineItem { + case is TextBasedRoomTimelineItem: + .overlay(capsuleStyle: false) + case let liveLocationTimelineItem as LiveLocationRoomTimelineItem: + liveLocationTimelineItem.layout + case let message as EventBasedMessageTimelineItemProtocol: + switch message { + case is ImageRoomTimelineItem, is VideoRoomTimelineItem: + .overlay(capsuleStyle: !message.hasMediaCaption) + case is AudioRoomTimelineItem, is FileRoomTimelineItem: + // swiftlint:disable:next void_function_in_ternary + message.hasMediaCaption ? .overlay(capsuleStyle: false) : .horizontal(spacing: 0) // No spacing as the content already contains it. + case let locationTimelineItem as LocationRoomTimelineItem: + .overlay(capsuleStyle: locationTimelineItem.content.geoURI != nil) + default: + .horizontal() + } + case is StickerRoomTimelineItem: + .overlay(capsuleStyle: true) + case is PollRoomTimelineItem: + .vertical(spacing: 16) default: .horizontal() } - case is StickerRoomTimelineItem: - .overlay(capsuleStyle: true) - case is PollRoomTimelineItem: - .vertical(spacing: 16) - default: - .horizontal() } } } diff --git a/ElementX/Sources/Screens/Timeline/View/Supplementary/TimelineReadReceiptsView.swift b/ElementX/Sources/Screens/Timeline/View/Supplementary/TimelineReadReceiptsView.swift index e1f712bd6..1d1426ceb 100644 --- a/ElementX/Sources/Screens/Timeline/View/Supplementary/TimelineReadReceiptsView.swift +++ b/ElementX/Sources/Screens/Timeline/View/Supplementary/TimelineReadReceiptsView.swift @@ -39,7 +39,7 @@ struct TimelineReadReceiptsView: View { } .accessibilityElement(children: .ignore) .accessibilityLabel(accessibilityLabel) - .accessibilityHint(L10n.a11yReadReceiptsTapToShowAll) + .accessibilityHint(L10n.a11yReadReceiptsTapToShowAllIos) } private var remaining: Int { diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/AudioRoomTimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/AudioRoomTimelineView.swift index 97f1ea315..86e72ee09 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/AudioRoomTimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/AudioRoomTimelineView.swift @@ -21,7 +21,9 @@ struct AudioRoomTimelineView: View { formattedCaption: timelineItem.content.formattedCaption, trailingReservedSize: timelineItem.trailingReservedSize, shouldBoost: timelineItem.shouldBoost, - isAudioFile: true) { + isAudioFile: true, + contentScannerService: context?.contentScannerService, + mediaSource: timelineItem.content.source) { context?.send(viewAction: .mediaTapped(itemID: timelineItem.id)) } .accessibilityLabel(L10n.commonAudio) @@ -31,6 +33,8 @@ struct AudioRoomTimelineView: View { struct AudioRoomTimelineView_Previews: PreviewProvider, TestablePreview { static let viewModel = TimelineViewModel.mock + static let scanningViewModel = TimelineViewModel.mock(contentScannerService: ContentScannerServiceMock(.init(scanResult: nil))) + static let unsafeViewModel = TimelineViewModel.mock(contentScannerService: ContentScannerServiceMock(.init(scanResult: false))) static var previews: some View { VStack(spacing: 20) { @@ -42,6 +46,22 @@ struct AudioRoomTimelineView_Previews: PreviewProvider, TestablePreview { caption: "This song rocks!")) } .environmentObject(viewModel.context) + + VStack(spacing: 20) { + AudioRoomTimelineView(timelineItem: makeItem(filename: "scanning.ogg", + fileSize: 2 * 1024 * 1024, + caption: "The audio is being scanned.")) + .environmentObject(scanningViewModel.context) + .environment(\.timelineContext, scanningViewModel.context) + + AudioRoomTimelineView(timelineItem: makeItem(filename: "unsafe.ogg", + fileSize: 2 * 1024 * 1024, + caption: "The audio is not safe.")) + .environmentObject(unsafeViewModel.context) + .environment(\.timelineContext, unsafeViewModel.context) + } + .environmentObject(viewModel.context) + .previewDisplayName("Content Scanner") } static func makeItem(filename: String, fileSize: UInt, caption: String? = nil) -> AudioRoomTimelineItem { @@ -55,7 +75,7 @@ struct AudioRoomTimelineView_Previews: PreviewProvider, TestablePreview { caption: caption, duration: 300, waveform: nil, - source: nil, + source: try? MediaSourceProxy(url: .mockMXCAudio, mimeType: nil), fileSize: fileSize, contentType: nil)) } diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/ContentScanningFailureView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/ContentScanningFailureView.swift new file mode 100644 index 000000000..eccab9c6b --- /dev/null +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/ContentScanningFailureView.swift @@ -0,0 +1,64 @@ +// +// Copyright 2026 Element Creations Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. +// Please see LICENSE files in the repository root for full details. +// + +import Compound +import SwiftUI + +/// The placeholder shown in place of a media item when the content scanner wasn't able to +/// validate it. Preview and download of the content are disabled. +/// +/// This view only renders the failure title and message - the critical background is applied +/// to the whole message bubble by `TimelineItemBubbledStylerView`, so that any caption and +/// reply preview are included in the critical area too. +struct ContentScanningFailureView: View { + let failure: ContentScanningFailure + + var body: some View { + HStack(spacing: 8) { + CompoundIcon(\.error, size: .medium, relativeTo: .compound.bodyMDSemibold) + .foregroundStyle(.compound.iconCriticalPrimary) + + VStack(alignment: .leading, spacing: 2) { + Text(title) + .font(.compound.bodyMDSemibold) + .foregroundStyle(.compound.textCriticalPrimary) + + Text(message) + .font(.compound.bodySM) + .foregroundStyle(.compound.textSecondary) + } + } + .accessibilityElement(children: .combine) + } + + private var title: String { + switch failure { + case .notSafe: L10n.contentScannerUnsafeTitle + case .notFound: L10n.contentScannerNotFoundTitle + } + } + + private var message: String { + switch failure { + case .notSafe: L10n.contentScannerUnsafeMessage + case .notFound: L10n.contentScannerNotFound + } + } +} + +// MARK: - Previews + +struct ContentScanningFailureView_Previews: PreviewProvider, TestablePreview { + static var previews: some View { + VStack(spacing: 16) { + ContentScanningFailureView(failure: .notSafe) + ContentScanningFailureView(failure: .notFound) + } + .frame(width: 260) + .padding() + } +} diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/FileRoomTimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/FileRoomTimelineView.swift index 1a44bc67e..c865a29cd 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/FileRoomTimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/FileRoomTimelineView.swift @@ -20,7 +20,9 @@ struct FileRoomTimelineView: View { caption: timelineItem.content.caption, formattedCaption: timelineItem.content.formattedCaption, trailingReservedSize: timelineItem.trailingReservedSize, - shouldBoost: timelineItem.shouldBoost) { + shouldBoost: timelineItem.shouldBoost, + contentScannerService: context?.contentScannerService, + mediaSource: timelineItem.content.source) { context?.send(viewAction: .mediaTapped(itemID: timelineItem.id)) } .accessibilityLabel(L10n.commonFile) @@ -38,6 +40,8 @@ struct MediaFileRoomTimelineContent: View { var trailingReservedSize: CGSize = .zero var shouldBoost = false var isAudioFile = false + var contentScannerService: ContentScannerServiceProtocol? + var mediaSource: MediaSourceProxy? private var fileDescription: String { var fileDescription = "\(filename.validatedFileExtension.uppercased())" @@ -55,13 +59,18 @@ struct MediaFileRoomTimelineContent: View { var body: some View { VStack(alignment: .leading, spacing: 8) { - if let onMediaTap { - filePreview - .onTapGesture { - onMediaTap() - } - } else { - filePreview + ContentScanningView(contentScannerService: contentScannerService, + mediaSource: mediaSource) { + if let onMediaTap { + filePreview(isScanning: false) + .onTapGesture(perform: onMediaTap) + } else { + filePreview(isScanning: false) + } + } scanningContent: { + filePreview(isScanning: true) + } unsafeContent: { failure in + ContentScanningFailureView(failure: failure) } if let formattedCaption { @@ -76,7 +85,7 @@ struct MediaFileRoomTimelineContent: View { } } - var filePreview: some View { + func filePreview(isScanning: Bool) -> some View { Label { VStack(alignment: .leading, spacing: 0) { Text(filename) @@ -90,11 +99,18 @@ struct MediaFileRoomTimelineContent: View { .foregroundStyle(.compound.textPrimary) .lineLimit(2) } icon: { - CompoundIcon(icon, size: .medium, relativeTo: .body) - .foregroundColor(.compound.iconPrimary) - .scaledPadding(6) - .background(.compound.iconOnSolidPrimary, - in: RoundedRectangle(cornerRadius: 4, style: .continuous)) + Group { + if isScanning { + ProgressView() + .scaledFrame(size: CompoundIcon.Size.medium.value, relativeTo: .body) + } else { + CompoundIcon(icon, size: .medium, relativeTo: .body) + .foregroundColor(.compound.iconPrimary) + } + } + .scaledPadding(6) + .background(.compound.iconOnSolidPrimary, + in: RoundedRectangle(cornerRadius: 4, style: .continuous)) } .labelStyle(.custom(spacing: 8, alignment: .center)) .padding(.horizontal, 4) // Add to the styler's padding of 8, as we use the default insets for the caption. @@ -105,6 +121,8 @@ struct MediaFileRoomTimelineContent: View { struct FileRoomTimelineView_Previews: PreviewProvider, TestablePreview { static let viewModel = TimelineViewModel.mock + static let scanningViewModel = TimelineViewModel.mock(contentScannerService: ContentScannerServiceMock(.init(scanResult: nil))) + static let unsafeViewModel = TimelineViewModel.mock(contentScannerService: ContentScannerServiceMock(.init(scanResult: false))) static var previews: some View { VStack(spacing: 20.0) { @@ -126,6 +144,21 @@ struct FileRoomTimelineView_Previews: PreviewProvider, TestablePreview { formattedCaption: "Formatted caption")) } .environmentObject(viewModel.context) + + VStack(spacing: 20.0) { + FileRoomTimelineView(timelineItem: makeItem(filename: "scanning.pdf", + fileSize: 3 * 1024 * 1024, + caption: "The file is being scanned.")) + .environmentObject(scanningViewModel.context) + .environment(\.timelineContext, scanningViewModel.context) + + FileRoomTimelineView(timelineItem: makeItem(filename: "unsafe.pdf", + fileSize: 3 * 1024 * 1024, + caption: "The file is not safe.")) + .environmentObject(unsafeViewModel.context) + .environment(\.timelineContext, unsafeViewModel.context) + } + .previewDisplayName("Content Scanner") } static func makeItem(filename: String, @@ -141,7 +174,7 @@ struct FileRoomTimelineView_Previews: PreviewProvider, TestablePreview { content: .init(filename: filename, caption: caption, formattedCaption: formattedCaption, - source: nil, + source: try? MediaSourceProxy(url: .mockMXCFile, mimeType: nil), fileSize: fileSize, thumbnailSource: nil, contentType: nil)) diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/ImageRoomTimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/ImageRoomTimelineView.swift index b9cd961a8..eec6c4a2c 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/ImageRoomTimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/ImageRoomTimelineView.swift @@ -13,33 +13,51 @@ struct ImageRoomTimelineView: View { @Environment(\.timelineContext) private var context let timelineItem: ImageRoomTimelineItem + @State private var contentScanningFailure: ContentScanningFailure? + var hasMediaCaption: Bool { timelineItem.content.caption != nil } var body: some View { TimelineStyler(timelineItem: timelineItem) { - VStack(alignment: .leading, spacing: 4) { - loadableImage - .accessibilityElement(children: .ignore) - .accessibilityLabel(L10n.commonImage) - // This clip shape is distinct from the one in the styler as that one - // operates on the entire message so wouldn't round the bottom corners. - .clipShape(RoundedRectangle(cornerRadius: hasMediaCaption ? 6 : 0)) - .onTapGesture { - context?.send(viewAction: .mediaTapped(itemID: timelineItem.id)) - } - - if let attributedCaption = timelineItem.content.formattedCaption { - FormattedBodyText(attributedString: attributedCaption, - trailingReservedSize: timelineItem.trailingReservedSize, - boostFontSize: timelineItem.shouldBoost) - } else if let caption = timelineItem.content.caption { - FormattedBodyText(text: caption, - trailingReservedSize: timelineItem.trailingReservedSize, - boostFontSize: timelineItem.shouldBoost) + // The caption sits 8pts below the content scanner failure placeholder, 4pts below the media. + VStack(alignment: .leading, spacing: contentScanningFailure == nil ? 4 : 8) { + ContentScanningView(contentScannerService: context?.contentScannerService, + mediaSource: timelineItem.content.imageInfo.source) { + loadableImage + .accessibilityElement(children: .ignore) + .accessibilityLabel(L10n.commonImage) + // This clip shape is distinct from the one in the styler as that one + // operates on the entire message so wouldn't round the bottom corners. + .clipShape(RoundedRectangle(cornerRadius: hasMediaCaption ? 6 : 0)) + .onTapGesture { + context?.send(viewAction: .mediaTapped(itemID: timelineItem.id)) + } + } scanningContent: { + placeholder + .overlay { ProgressView() } + .timelineMediaFrame(imageInfo: timelineItem.content.thumbnailInfo ?? timelineItem.content.imageInfo) + } unsafeContent: { failure in + ContentScanningFailureView(failure: failure) } + + caption } + .onPreferenceChange(ContentScanningFailurePreferenceKey.self) { contentScanningFailure = $0 } + } + } + + @ViewBuilder + private var caption: some View { + if let attributedCaption = timelineItem.content.formattedCaption { + FormattedBodyText(attributedString: attributedCaption, + trailingReservedSize: timelineItem.trailingReservedSize, + boostFontSize: timelineItem.shouldBoost) + } else if let caption = timelineItem.content.caption { + FormattedBodyText(text: caption, + trailingReservedSize: timelineItem.trailingReservedSize, + boostFontSize: timelineItem.shouldBoost) } } @@ -75,6 +93,8 @@ struct ImageRoomTimelineView: View { struct ImageRoomTimelineView_Previews: PreviewProvider, TestablePreview { static let viewModel = TimelineViewModel.mock + static let scanningViewModel = TimelineViewModel.mock(contentScannerService: ContentScannerServiceMock(.init(scanResult: nil))) + static let unsafeViewModel = TimelineViewModel.mock(contentScannerService: ContentScannerServiceMock(.init(scanResult: false))) static var previews: some View { ScrollView { @@ -93,6 +113,17 @@ struct ImageRoomTimelineView_Previews: PreviewProvider, TestablePreview { .environment(\.timelineContext, viewModel.context) .previewLayout(.fixed(width: 390, height: 1200)) .padding(.bottom, 20) + + VStack(spacing: 20.0) { + ImageRoomTimelineView(timelineItem: makeTimelineItem()) + .environmentObject(scanningViewModel.context) + .environment(\.timelineContext, scanningViewModel.context) + ImageRoomTimelineView(timelineItem: makeTimelineItem(caption: "This is an unsafe image.")) + .environmentObject(unsafeViewModel.context) + .environment(\.timelineContext, unsafeViewModel.context) + } + .environmentObject(viewModel.context) + .previewDisplayName("Content Scanner") } private static func makeTimelineItem(caption: String? = nil, isEdited: Bool = false) -> ImageRoomTimelineItem { diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/StickerRoomTimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/StickerRoomTimelineView.swift index 765d41d1e..00db3ce0b 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/StickerRoomTimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/StickerRoomTimelineView.swift @@ -15,18 +15,27 @@ struct StickerRoomTimelineView: View { var body: some View { TimelineStyler(timelineItem: timelineItem) { - LoadableImage(mediaSource: timelineItem.imageInfo.source, - mediaType: .timelineItem(uniqueID: timelineItem.id.uniqueID), - blurhash: timelineItem.blurhash, - size: timelineItem.imageInfo.size, - mediaProvider: context?.mediaProvider) { + ContentScanningView(contentScannerService: context?.contentScannerService, + mediaSource: timelineItem.imageInfo.source) { + LoadableImage(mediaSource: timelineItem.imageInfo.source, + mediaType: .timelineItem(uniqueID: timelineItem.id.uniqueID), + blurhash: timelineItem.blurhash, + size: timelineItem.imageInfo.size, + mediaProvider: context?.mediaProvider) { + placeholder + } + .timelineMediaFrame(imageInfo: timelineItem.imageInfo) + .accessibilityElement(children: .ignore) + .accessibilityLabel("\(L10n.commonSticker), \(timelineItem.body)") + .onTapGesture { + context?.send(viewAction: .mediaTapped(itemID: timelineItem.id)) + } + } scanningContent: { placeholder - } - .timelineMediaFrame(imageInfo: timelineItem.imageInfo) - .accessibilityElement(children: .ignore) - .accessibilityLabel("\(L10n.commonSticker), \(timelineItem.body)") - .onTapGesture { - context?.send(viewAction: .mediaTapped(itemID: timelineItem.id)) + .overlay { ProgressView() } + .timelineMediaFrame(imageInfo: timelineItem.imageInfo) + } unsafeContent: { failure in + ContentScanningFailureView(failure: failure) } } } @@ -40,29 +49,41 @@ struct StickerRoomTimelineView: View { struct StickerRoomTimelineView_Previews: PreviewProvider, TestablePreview { static let viewModel = TimelineViewModel.mock + static let scanningViewModel = TimelineViewModel.mock(contentScannerService: ContentScannerServiceMock(.init(scanResult: nil))) + static let unsafeViewModel = TimelineViewModel.mock(contentScannerService: ContentScannerServiceMock(.init(scanResult: false))) static var previews: some View { VStack(spacing: 20.0) { - StickerRoomTimelineView(timelineItem: StickerRoomTimelineItem(id: .randomEvent, - body: "Some image", - timestamp: .mock, - isOutgoing: false, - isEditable: false, - canBeRepliedTo: true, - sender: .init(id: "Bob"), - imageInfo: .mockImage)) + StickerRoomTimelineView(timelineItem: makeTimelineItem(body: "Some image")) - StickerRoomTimelineView(timelineItem: StickerRoomTimelineItem(id: .randomEvent, - body: "Blurhashed image", - timestamp: .mock, - isOutgoing: false, - isEditable: false, - canBeRepliedTo: true, - sender: .init(id: "Bob"), - imageInfo: .mockImage, - blurhash: "L%KUc%kqS$RP?Ks,WEf8OlrqaekW")) + StickerRoomTimelineView(timelineItem: makeTimelineItem(body: "Blurhashed image", + blurhash: "L%KUc%kqS$RP?Ks,WEf8OlrqaekW")) } .environmentObject(viewModel.context) .environment(\.timelineContext, viewModel.context) + + VStack(spacing: 20.0) { + StickerRoomTimelineView(timelineItem: makeTimelineItem(body: "Scanning image")) + .environmentObject(scanningViewModel.context) + .environment(\.timelineContext, scanningViewModel.context) + + StickerRoomTimelineView(timelineItem: makeTimelineItem(body: "Unsafe image")) + .environmentObject(unsafeViewModel.context) + .environment(\.timelineContext, unsafeViewModel.context) + } + .environmentObject(viewModel.context) + .previewDisplayName("Content Scanner") + } + + private static func makeTimelineItem(body: String, blurhash: String? = nil) -> StickerRoomTimelineItem { + StickerRoomTimelineItem(id: .randomEvent, + body: body, + timestamp: .mock, + isOutgoing: false, + isEditable: false, + canBeRepliedTo: true, + sender: .init(id: "Bob"), + imageInfo: .mockImage, + blurhash: blurhash) } } diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/VideoRoomTimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/VideoRoomTimelineView.swift index a95fc07e3..e0bc96cda 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/VideoRoomTimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/VideoRoomTimelineView.swift @@ -13,23 +13,35 @@ struct VideoRoomTimelineView: View { @Environment(\.timelineContext) private var context let timelineItem: VideoRoomTimelineItem + @State private var contentScanningFailure: ContentScanningFailure? + private var hasMediaCaption: Bool { timelineItem.content.caption != nil } var body: some View { TimelineStyler(timelineItem: timelineItem) { - VStack(alignment: .leading, spacing: 4) { - thumbnail - .timelineMediaFrame(imageInfo: timelineItem.content.thumbnailInfo) - .accessibilityElement(children: .ignore) - .accessibilityLabel(L10n.commonVideo) - // This clip shape is distinct from the one in the styler as that one - // operates on the entire message so wouldn't round the bottom corners. - .clipShape(RoundedRectangle(cornerRadius: hasMediaCaption ? 6 : 0)) - .onTapGesture { - context?.send(viewAction: .mediaTapped(itemID: timelineItem.id)) - } + // The caption sits 8pts below the content scanner failure placeholder, 4pts below the media. + VStack(alignment: .leading, spacing: contentScanningFailure == nil ? 4 : 8) { + ContentScanningView(contentScannerService: context?.contentScannerService, + mediaSource: timelineItem.content.videoInfo.source) { + thumbnail + .timelineMediaFrame(imageInfo: timelineItem.content.thumbnailInfo) + .accessibilityElement(children: .ignore) + .accessibilityLabel(L10n.commonVideo) + // This clip shape is distinct from the one in the styler as that one + // operates on the entire message so wouldn't round the bottom corners. + .clipShape(RoundedRectangle(cornerRadius: hasMediaCaption ? 6 : 0)) + .onTapGesture { + context?.send(viewAction: .mediaTapped(itemID: timelineItem.id)) + } + } scanningContent: { + placeholder + .overlay { ProgressView() } + .timelineMediaFrame(imageInfo: timelineItem.content.thumbnailInfo) + } unsafeContent: { failure in + ContentScanningFailureView(failure: failure) + } if let attributedCaption = timelineItem.content.formattedCaption { FormattedBodyText(attributedString: attributedCaption, @@ -41,6 +53,7 @@ struct VideoRoomTimelineView: View { boostFontSize: timelineItem.shouldBoost) } } + .onPreferenceChange(ContentScanningFailurePreferenceKey.self) { contentScanningFailure = $0 } } } @@ -83,6 +96,8 @@ struct VideoRoomTimelineView: View { struct VideoRoomTimelineView_Previews: PreviewProvider, TestablePreview { static let viewModel = TimelineViewModel.mock + static let scanningViewModel = TimelineViewModel.mock(contentScannerService: ContentScannerServiceMock(.init(scanResult: nil))) + static let unsafeViewModel = TimelineViewModel.mock(contentScannerService: ContentScannerServiceMock(.init(scanResult: false))) static var previews: some View { ScrollView { @@ -101,6 +116,17 @@ struct VideoRoomTimelineView_Previews: PreviewProvider, TestablePreview { .environment(\.timelineContext, viewModel.context) .previewLayout(.fixed(width: 390, height: 975)) .padding(.bottom, 20) + + VStack(spacing: 20.0) { + VideoRoomTimelineView(timelineItem: makeTimelineItem()) + .environmentObject(scanningViewModel.context) + .environment(\.timelineContext, scanningViewModel.context) + VideoRoomTimelineView(timelineItem: makeTimelineItem(caption: "This is an unsafe video.")) + .environmentObject(unsafeViewModel.context) + .environment(\.timelineContext, unsafeViewModel.context) + } + .environmentObject(viewModel.context) + .previewDisplayName("Content Scanner") } private static func makeTimelineItem(caption: String? = nil, isEdited: Bool = false) -> VideoRoomTimelineItem { diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/VoiceMessageRoomTimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/VoiceMessageRoomTimelineView.swift index bfe5326b4..8df5a3fbb 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/VoiceMessageRoomTimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/VoiceMessageRoomTimelineView.swift @@ -30,12 +30,25 @@ struct VoiceMessageRoomTimelineContent: View { let playerState: AudioPlayerState var body: some View { - VoiceMessageRoomPlaybackView(playerState: playerState, - onPlayPause: onPlaybackPlayPause, - onSeek: { onPlaybackSeek($0) }, - onScrubbing: { onPlaybackScrubbing($0) }, - onPlaybackSpeedChange: onPlaybackSpeedChange) - .fixedSize(horizontal: false, vertical: true) + ContentScanningView(contentScannerService: context?.contentScannerService, + mediaSource: timelineItem.content.source) { + VoiceMessageRoomPlaybackView(playerState: playerState, + onPlayPause: onPlaybackPlayPause, + onSeek: { onPlaybackSeek($0) }, + onScrubbing: { onPlaybackScrubbing($0) }, + onPlaybackSpeedChange: onPlaybackSpeedChange) + .fixedSize(horizontal: false, vertical: true) + } scanningContent: { + VoiceMessageRoomPlaybackView(playerState: playerState, + isScanning: true, + onPlayPause: { }, + onSeek: { _ in }, + onScrubbing: { _ in }, + onPlaybackSpeedChange: { }) + .fixedSize(horizontal: false, vertical: true) + } unsafeContent: { failure in + ContentScanningFailureView(failure: failure) + } } private func onPlaybackPlayPause() { @@ -67,6 +80,8 @@ struct VoiceMessageRoomTimelineContent: View { struct VoiceMessageRoomTimelineView_Previews: PreviewProvider, TestablePreview { static let viewModel = TimelineViewModel.mock + static let scanningViewModel = TimelineViewModel.mock(contentScannerService: ContentScannerServiceMock(.init(scanResult: nil))) + static let unsafeViewModel = TimelineViewModel.mock(contentScannerService: ContentScannerServiceMock(.init(scanResult: false))) static let timelineItemIdentifier = TimelineItemIdentifier.randomEvent static let voiceRoomTimelineItem = VoiceMessageRoomTimelineItem(id: timelineItemIdentifier, timestamp: .mock, @@ -77,7 +92,7 @@ struct VoiceMessageRoomTimelineView_Previews: PreviewProvider, TestablePreview { content: .init(filename: "audio.ogg", duration: 300, waveform: EstimatedWaveform.mockWaveform, - source: nil, + source: try? MediaSourceProxy(url: .mockMXCAudio, mimeType: nil), fileSize: nil, contentType: nil)) @@ -89,6 +104,19 @@ struct VoiceMessageRoomTimelineView_Previews: PreviewProvider, TestablePreview { static var previews: some View { body.environmentObject(viewModel.context) + + VStack(spacing: 20) { + VoiceMessageRoomTimelineView(timelineItem: voiceRoomTimelineItem, playerState: playerState) + .environmentObject(scanningViewModel.context) + .environment(\.timelineContext, scanningViewModel.context) + + VoiceMessageRoomTimelineView(timelineItem: voiceRoomTimelineItem, playerState: playerState) + .environmentObject(unsafeViewModel.context) + .environment(\.timelineContext, unsafeViewModel.context) + } + .fixedSize(horizontal: false, vertical: true) + .environmentObject(viewModel.context) + .previewDisplayName("Content Scanner") } static var body: some View { diff --git a/ElementX/Sources/Screens/Timeline/View/VoiceMessageRoomPlaybackView.swift b/ElementX/Sources/Screens/Timeline/View/VoiceMessageRoomPlaybackView.swift index fcd6aef26..7a0ea7649 100644 --- a/ElementX/Sources/Screens/Timeline/View/VoiceMessageRoomPlaybackView.swift +++ b/ElementX/Sources/Screens/Timeline/View/VoiceMessageRoomPlaybackView.swift @@ -16,6 +16,10 @@ struct VoiceMessageRoomPlaybackView: View { @ScaledMetric private var waveformLinePadding = 2.0 @GestureState var isDragging = false + /// Whether the voice message is being scanned by the content scanner, in which case + /// the play button shows a spinner and playback isn't available (which prevents the audio file from being downloaded). + var isScanning = false + let onPlayPause: () -> Void let onSeek: (Double) -> Void let onScrubbing: (Bool) -> Void @@ -23,7 +27,7 @@ struct VoiceMessageRoomPlaybackView: View { var body: some View { HStack(spacing: 8) { - VoiceMessageButton(state: .init(playerState.playerButtonPlaybackState), + VoiceMessageButton(state: isScanning ? .loading : .init(playerState.playerButtonPlaybackState), size: .medium, action: onPlayPause) VStack(spacing: 2) { diff --git a/PreviewTests/Sources/GeneratedPreviewTests.swift b/PreviewTests/Sources/GeneratedPreviewTests.swift index 9430626f3..835cae7f6 100644 --- a/PreviewTests/Sources/GeneratedPreviewTests.swift +++ b/PreviewTests/Sources/GeneratedPreviewTests.swift @@ -172,6 +172,13 @@ extension PreviewTests { } } + @Test + func contentScanningFailureView() async throws { + for (index, preview) in ContentScanningFailureView_Previews._allPreviews.enumerated() { + try await assertSnapshots(matching: preview, step: index) + } + } + @Test func copyTextButton() async throws { for (index, preview) in CopyTextButton_Previews._allPreviews.enumerated() { diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/audioRoomTimelineView.Content-Scanner-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/audioRoomTimelineView.Content-Scanner-iPad-en-GB.png new file mode 100644 index 000000000..f9d593e50 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/audioRoomTimelineView.Content-Scanner-iPad-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65daa24e31bd0cf9f8b729a4d725cf5cb798d58bd955e1e2fe18d367c6ac22f7 +size 111447 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/audioRoomTimelineView.Content-Scanner-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/audioRoomTimelineView.Content-Scanner-iPad-pseudo.png new file mode 100644 index 000000000..be2ed9711 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/audioRoomTimelineView.Content-Scanner-iPad-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e505ab7d0a5fdc71a8e1bf07e3cba7b053a71dafdfe4ffade41ea6dcb7bb0e7 +size 119312 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/audioRoomTimelineView.Content-Scanner-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/audioRoomTimelineView.Content-Scanner-iPhone-en-GB.png new file mode 100644 index 000000000..64e8893cb --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/audioRoomTimelineView.Content-Scanner-iPhone-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e97574e905757bab6d1c37b0b74a3f1dea65d8414f7f229842d6a37335185360 +size 67391 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/audioRoomTimelineView.Content-Scanner-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/audioRoomTimelineView.Content-Scanner-iPhone-pseudo.png new file mode 100644 index 000000000..3131f2bfc --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/audioRoomTimelineView.Content-Scanner-iPhone-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:39d3a1d25f637f602cefde236633ac8443a5dc4f5775c9deb0b4256e9e117d71 +size 77453 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/contentScanningFailureView.iPad-en-GB-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/contentScanningFailureView.iPad-en-GB-0.png new file mode 100644 index 000000000..da97112df --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/contentScanningFailureView.iPad-en-GB-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7de83b005f4c5290c77e0b270c955f67670a4edf8184d191c091ae3d8b934534 +size 89345 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/contentScanningFailureView.iPad-pseudo-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/contentScanningFailureView.iPad-pseudo-0.png new file mode 100644 index 000000000..ed1e484d1 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/contentScanningFailureView.iPad-pseudo-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:00af60cc60b8e60dbe765cefd9d75f5548d4e7008053795cd647291a21fa0e24 +size 103800 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/contentScanningFailureView.iPhone-en-GB-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/contentScanningFailureView.iPhone-en-GB-0.png new file mode 100644 index 000000000..4b5f8225e --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/contentScanningFailureView.iPhone-en-GB-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b6e06014fdfe62eb843da48f30d105e2b4588eb43a2f6be70a4408b4343c792 +size 46916 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/contentScanningFailureView.iPhone-pseudo-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/contentScanningFailureView.iPhone-pseudo-0.png new file mode 100644 index 000000000..4f173abcc --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/contentScanningFailureView.iPhone-pseudo-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cfa2cf6ed783a1bfa57949c21305ba2f32d77c6dd9fd616721e11c9612030ef4 +size 59329 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/fileRoomTimelineView.Content-Scanner-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/fileRoomTimelineView.Content-Scanner-iPad-en-GB.png new file mode 100644 index 000000000..f5d94e700 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/fileRoomTimelineView.Content-Scanner-iPad-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9cf7cea65331f4916582cc81f765efb2f065a3b0905b9c2a0737a2a97ee76fea +size 110083 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/fileRoomTimelineView.Content-Scanner-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/fileRoomTimelineView.Content-Scanner-iPad-pseudo.png new file mode 100644 index 000000000..f9878a046 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/fileRoomTimelineView.Content-Scanner-iPad-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:abcc9c5a4e9bcfad13f04f221bc52c6594583e41b190156f7daf0dc8dddfb6d9 +size 117971 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/fileRoomTimelineView.Content-Scanner-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/fileRoomTimelineView.Content-Scanner-iPhone-en-GB.png new file mode 100644 index 000000000..a82f85502 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/fileRoomTimelineView.Content-Scanner-iPhone-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:264fd91dbadbd8f8c42b43b9767e9c948e6ce7433accf15649299df5f6da7c7b +size 65882 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/fileRoomTimelineView.Content-Scanner-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/fileRoomTimelineView.Content-Scanner-iPhone-pseudo.png new file mode 100644 index 000000000..dc7d489f0 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/fileRoomTimelineView.Content-Scanner-iPhone-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb5597e4068057e68013e7c02490b455871ebc2ec2c5c67fd65d356c72d9eca3 +size 75424 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/imageRoomTimelineView.Content-Scanner-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/imageRoomTimelineView.Content-Scanner-iPad-en-GB.png new file mode 100644 index 000000000..9c145d7be --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/imageRoomTimelineView.Content-Scanner-iPad-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c7a42aff861071be9f97b2c735cae29eea94665e42fd6be9bc9df49d9db3457c +size 102620 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/imageRoomTimelineView.Content-Scanner-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/imageRoomTimelineView.Content-Scanner-iPad-pseudo.png new file mode 100644 index 000000000..f288b5609 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/imageRoomTimelineView.Content-Scanner-iPad-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:080a0d612ea42f8559da49a3a190b443db6a110a0933717d18fc5adfa423c9da +size 109680 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/imageRoomTimelineView.Content-Scanner-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/imageRoomTimelineView.Content-Scanner-iPhone-en-GB.png new file mode 100644 index 000000000..e7fe41419 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/imageRoomTimelineView.Content-Scanner-iPhone-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:147f7f2fd61429eff7e1368231aa43b56ac7373617c90f9ab91aaa8da3135978 +size 56664 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/imageRoomTimelineView.Content-Scanner-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/imageRoomTimelineView.Content-Scanner-iPhone-pseudo.png new file mode 100644 index 000000000..c3f8905ca --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/imageRoomTimelineView.Content-Scanner-iPhone-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:70be34bbd75a2955125202a3411a519106ec08c08a5a6307297daebdd2419901 +size 67498 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Applying-change-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Applying-change-iPad-en-GB.png index ee158dc9d..60e88b9bf 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Applying-change-iPad-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Applying-change-iPad-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:65ab7c901ea0a4d1b07999e0a0d2df40f0983f5f9b443c61a27d1aa4f413ab96 -size 104323 +oid sha256:7215700c390160c0d89b1fec6c1c4ba2ad275686f5a55fb1fa6af4e28f852f96 +size 103726 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Applying-change-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Applying-change-iPad-pseudo.png index bc5538b1c..21146f433 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Applying-change-iPad-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Applying-change-iPad-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c25001b8af4b9200f5648ac360d7b26dfd5f7e13dcf6ec9c9b4806eeb34c6186 -size 118399 +oid sha256:8462b5eb394ebe57d41fc16159ffe6b39025c98081451a088364407908229f01 +size 117683 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Applying-change-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Applying-change-iPhone-en-GB.png index 5539d8de6..5657a6dcd 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Applying-change-iPhone-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Applying-change-iPhone-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5207545bb9767684f0203f37256dc99a66e153cb91b3fcaf0d5fef9484196784 -size 57926 +oid sha256:733c3d43ee0571f40777c5378f6d32c3ccc30fea859e0b5a10abfb8650fa2b8d +size 57436 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Applying-change-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Applying-change-iPhone-pseudo.png index 066bfccfc..ddc91061f 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Applying-change-iPhone-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Applying-change-iPhone-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2cb1214f55b480046b05fa515a485c621f6721c9978d966cbf615fbe966d7612 -size 79324 +oid sha256:4c245c89d9db7ceba59196857775bb66d3c00c75bb6261b498a58df982114e67 +size 77385 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Direct-Chats-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Direct-Chats-iPad-en-GB.png index 0aa0f3917..b75cfdbef 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Direct-Chats-iPad-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Direct-Chats-iPad-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9a74be5f10b6069527d4835d02bdb361c8ca42d059ea7c9254d318ae09b77f4e -size 104457 +oid sha256:f8505af6d122ebcb782c6d3d9fed36c17825dce88ae115e98a517ed43ff6f7b5 +size 103827 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Direct-Chats-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Direct-Chats-iPad-pseudo.png index 9587be348..da375cc32 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Direct-Chats-iPad-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Direct-Chats-iPad-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b4cb2770329ff98b1935fde6c93e0e81a51d6ae19a608b5579edcad838b64d8c -size 119070 +oid sha256:41f522c0b39d6162f2e16f56fb899fec74a1d5eaca426031aea1127da940ef27 +size 118132 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Direct-Chats-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Direct-Chats-iPhone-en-GB.png index 96f391a35..70d74daed 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Direct-Chats-iPhone-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Direct-Chats-iPhone-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:891e7337c733fc6b4a0ad5d6bd814968ea1aee4816615228e74624d4b9fd33ed -size 58362 +oid sha256:fb361a6b20735c980735cb59d17f5ff1903f597e97080e4c5ce71f61145e6069 +size 57696 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Direct-Chats-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Direct-Chats-iPhone-pseudo.png index 3fe5f9528..4696beafd 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Direct-Chats-iPhone-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Direct-Chats-iPhone-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fedffe6f1d37153701a1b9367a2d741689f6492e397a5417389a3d5c34f675a7 -size 80092 +oid sha256:cf8f5d300e94cb941735f4d33720b35225b3e45e92629a6f4a683f9778fc5942 +size 77665 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-Without-Disclaimer-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-Without-Disclaimer-iPad-en-GB.png index 6395a8603..c6331f499 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-Without-Disclaimer-iPad-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-Without-Disclaimer-iPad-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2bd069ba0540754cfb30969e0f4ce2e94de631d58646ac35fe8d7c66662bc03e -size 104513 +oid sha256:108c5554332df7a50211e69d1f398355ae343963c97c93bdfd9342f1c1e528b4 +size 103871 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-Without-Disclaimer-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-Without-Disclaimer-iPad-pseudo.png index a75304b9e..cec79e963 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-Without-Disclaimer-iPad-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-Without-Disclaimer-iPad-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0507c35e740560f65a1bdfb8b7dc8abf382fedb6f70ff18e537b33b929694f31 -size 117768 +oid sha256:e84ceb1dd61f72a9c3ec94cd8962cf458149f3dde45f3849706d82f1cf947b28 +size 116996 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-Without-Disclaimer-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-Without-Disclaimer-iPhone-en-GB.png index 84e374408..7ca816b03 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-Without-Disclaimer-iPhone-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-Without-Disclaimer-iPhone-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:97b455334031a640da9ece701875e4cce8194b4ef59dee4564e269a0cd010424 -size 59203 +oid sha256:c9b73a4c47938dd9258d9c02f255476dc0f6aeebf75ed79ea984ca3d3a4d2320 +size 58541 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-Without-Disclaimer-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-Without-Disclaimer-iPhone-pseudo.png index 79bb8a6e1..99aa312cb 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-Without-Disclaimer-iPhone-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-Without-Disclaimer-iPhone-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b4b5fc3bfeea57dc64896035a7ce6d79df38e6d56ad1726f264ba8f1e7fb871e -size 81753 +oid sha256:a262372a675ee77610959346887fa7fd760913047a1a84ef9c22159e45f1f82a +size 79519 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-iPad-en-GB.png index 6395a8603..c6331f499 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-iPad-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-iPad-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2bd069ba0540754cfb30969e0f4ce2e94de631d58646ac35fe8d7c66662bc03e -size 104513 +oid sha256:108c5554332df7a50211e69d1f398355ae343963c97c93bdfd9342f1c1e528b4 +size 103871 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-iPad-pseudo.png index a75304b9e..cec79e963 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-iPad-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-iPad-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0507c35e740560f65a1bdfb8b7dc8abf382fedb6f70ff18e537b33b929694f31 -size 117768 +oid sha256:e84ceb1dd61f72a9c3ec94cd8962cf458149f3dde45f3849706d82f1cf947b28 +size 116996 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-iPhone-en-GB.png index 84e374408..7ca816b03 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-iPhone-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-iPhone-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:97b455334031a640da9ece701875e4cce8194b4ef59dee4564e269a0cd010424 -size 59203 +oid sha256:c9b73a4c47938dd9258d9c02f255476dc0f6aeebf75ed79ea984ca3d3a4d2320 +size 58541 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-iPhone-pseudo.png index 79bb8a6e1..99aa312cb 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-iPhone-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/notificationSettingsEditScreen.Group-Chats-iPhone-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b4b5fc3bfeea57dc64896035a7ce6d79df38e6d56ad1726f264ba8f1e7fb871e -size 81753 +oid sha256:a262372a675ee77610959346887fa7fd760913047a1a84ef9c22159e45f1f82a +size 79519 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Encrypted-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Encrypted-iPad-en-GB.png index e9332da08..091ef5b8d 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Encrypted-iPad-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Encrypted-iPad-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ecc3fbf8b73c07a7be69f2668d5c97dfa406b74df8175557ed43ab10ab069f30 -size 104663 +oid sha256:82c84eba438609cf64f67469d57584eb03c7d4a8eabbebdb1e8fbea4f60bd16c +size 104083 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Encrypted-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Encrypted-iPad-pseudo.png index f45ce5ea0..8ca728b33 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Encrypted-iPad-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Encrypted-iPad-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3f705d62927a092377f67a21bd2d6c0605d7c8213138aa368386ad4496a052d0 -size 119587 +oid sha256:a1cb5963c1867d5dbd38d91667b98ffab6b6a83d474949ea258e6d4d609ae578 +size 118840 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Encrypted-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Encrypted-iPhone-en-GB.png index 8cb7bf031..8bbe47d5e 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Encrypted-iPhone-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Encrypted-iPhone-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5add993fc38b9e25bd8f33269fe69c3cd6424f9ac4f1834abad4121e20cc35fa -size 59200 +oid sha256:cd5e8a0682249277ce704a6a23cda13ebeea5ee0bf0656c84978a0f1f3776050 +size 58524 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Encrypted-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Encrypted-iPhone-pseudo.png index c9c11882f..d9bfa34da 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Encrypted-iPhone-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Encrypted-iPhone-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2cdae536ffa9143954cae633e5900ad93d5eacf21057a8c89c289f379b3507ef -size 82372 +oid sha256:fe2feb22eda8e2cc79d3a169ef535b12604ea3ec59e8f7677fe6af5fa11d0fac +size 78846 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Unencrypted-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Unencrypted-iPad-en-GB.png index e9332da08..091ef5b8d 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Unencrypted-iPad-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Unencrypted-iPad-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ecc3fbf8b73c07a7be69f2668d5c97dfa406b74df8175557ed43ab10ab069f30 -size 104663 +oid sha256:82c84eba438609cf64f67469d57584eb03c7d4a8eabbebdb1e8fbea4f60bd16c +size 104083 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Unencrypted-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Unencrypted-iPad-pseudo.png index f45ce5ea0..8ca728b33 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Unencrypted-iPad-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Unencrypted-iPad-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3f705d62927a092377f67a21bd2d6c0605d7c8213138aa368386ad4496a052d0 -size 119587 +oid sha256:a1cb5963c1867d5dbd38d91667b98ffab6b6a83d474949ea258e6d4d609ae578 +size 118840 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Unencrypted-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Unencrypted-iPhone-en-GB.png index 8cb7bf031..8bbe47d5e 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Unencrypted-iPhone-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Unencrypted-iPhone-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5add993fc38b9e25bd8f33269fe69c3cd6424f9ac4f1834abad4121e20cc35fa -size 59200 +oid sha256:cd5e8a0682249277ce704a6a23cda13ebeea5ee0bf0656c84978a0f1f3776050 +size 58524 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Unencrypted-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Unencrypted-iPhone-pseudo.png index c9c11882f..d9bfa34da 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Unencrypted-iPhone-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsCustomSectionView.Unencrypted-iPhone-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2cdae536ffa9143954cae633e5900ad93d5eacf21057a8c89c289f379b3507ef -size 82372 +oid sha256:fe2feb22eda8e2cc79d3a169ef535b12604ea3ec59e8f7677fe6af5fa11d0fac +size 78846 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Encrypted-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Encrypted-iPad-en-GB.png index 538b413e7..93f19866e 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Encrypted-iPad-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Encrypted-iPad-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b1c3a092778dd89916edf9e1762b189d618334c62916d430dcb7f02a26da0e9d -size 111447 +oid sha256:be58f78685af914af8ef1f8a5b3e6c1c5acf08fac266011fdbe46863f3c1bc06 +size 110771 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Encrypted-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Encrypted-iPad-pseudo.png index e8507b9d8..8530aeac8 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Encrypted-iPad-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Encrypted-iPad-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:45b3b85dd9a96d903bf599b837dfe3a23d956c7989af40cbacfffa8283fb748a -size 126731 +oid sha256:56d1d8fad0878c1436f0854e103a2203113ce7b7f6a51ee18726971dcc376c22 +size 126048 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Encrypted-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Encrypted-iPhone-en-GB.png index cd218c994..47f7f711a 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Encrypted-iPhone-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Encrypted-iPhone-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:70628211cd73a2d4e39f58467d577d5685126f90da9ba4b9413b90ed884e05c2 -size 65180 +oid sha256:e4fa3d1c6d1363c4168fca71292ccffcf5f13686cd6b8bd9341bffad37aa1d25 +size 64572 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Encrypted-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Encrypted-iPhone-pseudo.png index 5f58d47ed..8c154731a 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Encrypted-iPhone-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Encrypted-iPhone-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:63dcabfca6d5e294bc49e5518be0f8d3d65fbdbbe7e9ee59327137ced88c4575 -size 93723 +oid sha256:9a3aca0d1cbb658a1cb7632101a935a144e514a899eb0a2db1b01f8fa30a32b3 +size 90503 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Unencrypted-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Unencrypted-iPad-en-GB.png index 538b413e7..93f19866e 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Unencrypted-iPad-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Unencrypted-iPad-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b1c3a092778dd89916edf9e1762b189d618334c62916d430dcb7f02a26da0e9d -size 111447 +oid sha256:be58f78685af914af8ef1f8a5b3e6c1c5acf08fac266011fdbe46863f3c1bc06 +size 110771 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Unencrypted-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Unencrypted-iPad-pseudo.png index e8507b9d8..8530aeac8 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Unencrypted-iPad-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Unencrypted-iPad-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:45b3b85dd9a96d903bf599b837dfe3a23d956c7989af40cbacfffa8283fb748a -size 126731 +oid sha256:56d1d8fad0878c1436f0854e103a2203113ce7b7f6a51ee18726971dcc376c22 +size 126048 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Unencrypted-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Unencrypted-iPhone-en-GB.png index cd218c994..47f7f711a 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Unencrypted-iPhone-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Unencrypted-iPhone-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:70628211cd73a2d4e39f58467d577d5685126f90da9ba4b9413b90ed884e05c2 -size 65180 +oid sha256:e4fa3d1c6d1363c4168fca71292ccffcf5f13686cd6b8bd9341bffad37aa1d25 +size 64572 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Unencrypted-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Unencrypted-iPhone-pseudo.png index 5f58d47ed..8c154731a 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Unencrypted-iPhone-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/roomNotificationSettingsUserDefinedScreen.Unencrypted-iPhone-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:63dcabfca6d5e294bc49e5518be0f8d3d65fbdbbe7e9ee59327137ced88c4575 -size 93723 +oid sha256:9a3aca0d1cbb658a1cb7632101a935a144e514a899eb0a2db1b01f8fa30a32b3 +size 90503 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/searchScreen.Loaded-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/searchScreen.Loaded-iPad-en-GB.png deleted file mode 100644 index 5406ebdb9..000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/searchScreen.Loaded-iPad-en-GB.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:69ddd3c435d8c50a231bdc128c059127219d35624a16912579ce0f878830a274 -size 148724 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/searchScreen.Loaded-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/searchScreen.Loaded-iPad-pseudo.png deleted file mode 100644 index c76f439d9..000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/searchScreen.Loaded-iPad-pseudo.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:680e82d9b7877d868e734783ac7d9dc9c615d7c53a80b816e7529cbda6e3be0d -size 149586 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/searchScreen.Loaded-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/searchScreen.Loaded-iPhone-en-GB.png deleted file mode 100644 index dead27c49..000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/searchScreen.Loaded-iPhone-en-GB.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1a419004009267be00c3bb862408e82639e2fb45f6951e77da9309b1245f046d -size 103648 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/searchScreen.Loaded-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/searchScreen.Loaded-iPhone-pseudo.png deleted file mode 100644 index d1bb683f8..000000000 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/searchScreen.Loaded-iPhone-pseudo.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5a6a0b062ae788f675ada0f9011ff70dea366bf36c6b5d5a13a5bd909a03bc6c -size 104605 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/stickerRoomTimelineView.Content-Scanner-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/stickerRoomTimelineView.Content-Scanner-iPad-en-GB.png new file mode 100644 index 000000000..5b8ec9741 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/stickerRoomTimelineView.Content-Scanner-iPad-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6910db0f35343c964a3263bb875e73330f5e5ad32ebb5a57a3fe739c1719df3 +size 97392 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/stickerRoomTimelineView.Content-Scanner-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/stickerRoomTimelineView.Content-Scanner-iPad-pseudo.png new file mode 100644 index 000000000..f25f52a3b --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/stickerRoomTimelineView.Content-Scanner-iPad-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cbfd6addca1299a033ed07d2ba940b894db4529966790e5dac46a337c1ec1d1b +size 104975 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/stickerRoomTimelineView.Content-Scanner-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/stickerRoomTimelineView.Content-Scanner-iPhone-en-GB.png new file mode 100644 index 000000000..e3a355acf --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/stickerRoomTimelineView.Content-Scanner-iPhone-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c9b49824321958e528210f9615f2e29048f6f73167d49f6d72dc9eded795857 +size 52714 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/stickerRoomTimelineView.Content-Scanner-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/stickerRoomTimelineView.Content-Scanner-iPhone-pseudo.png new file mode 100644 index 000000000..33e8a27bd --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/stickerRoomTimelineView.Content-Scanner-iPhone-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f393472b85e6c6e1174d2e1eaee22731cfa2fb623d50b6bff4c8dc9ee3e5e9d3 +size 62543 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Replies-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Replies-iPad-en-GB.png index 64d03d3b6..422de0df2 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Replies-iPad-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Replies-iPad-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:86a734b74a8d7b4fae4c9455f5ef6f449c6804b7dec96e0f362eaaf6bae2c3fe -size 107948 +oid sha256:e39edab07bcc2e86378cd7b20ca03c57118ec04f5548a5cda729680e01ef3ee6 +size 136868 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Replies-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Replies-iPad-pseudo.png index f6f52c070..bcff51069 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Replies-iPad-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Replies-iPad-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ca5ec021c85aa9c24c682c5db929b73f7baa56d4c6dcb3e04f06e17ab33836e5 -size 109542 +oid sha256:f7cb8dc14692fa2817766fe8036a37b0a1d8e7a2d2fe45201cd25a2612e73a56 +size 145143 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Replies-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Replies-iPhone-en-GB.png index c530ade32..edfcee56c 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Replies-iPhone-en-GB.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Replies-iPhone-en-GB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:33ee078088ed44e3497cf33a2cc72eeb4a0bbd3c8537eb0b1a82d18c11264f0a -size 66260 +oid sha256:2a91badc1b4d5a0227464ddba3fb35e757a64365c9599e122697beb62f4361be +size 93094 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Replies-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Replies-iPhone-pseudo.png index 766a18487..78aadf191 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Replies-iPhone-pseudo.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/timelineItemBubbledStylerView.Replies-iPhone-pseudo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:637d6d91ec261adfc56c5202aeb9a23f46c89bdc119c9e1cbe9989c49060ca4b -size 67367 +oid sha256:3f583508ac5f41bb91a5000610b0419e3bb3315ef23c8262853fd887ba320678 +size 103536 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/videoRoomTimelineView.Content-Scanner-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/videoRoomTimelineView.Content-Scanner-iPad-en-GB.png new file mode 100644 index 000000000..921384f77 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/videoRoomTimelineView.Content-Scanner-iPad-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:492dd038b467f4db9661e9f2cb721022f9f837103f4130c27e71fb03e5cdfbb2 +size 103177 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/videoRoomTimelineView.Content-Scanner-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/videoRoomTimelineView.Content-Scanner-iPad-pseudo.png new file mode 100644 index 000000000..1226a97e4 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/videoRoomTimelineView.Content-Scanner-iPad-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b87d8652edb8c23a296dc9dbabf1a5b054d1df6abdbaebd32cd8fd11a2f43805 +size 110250 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/videoRoomTimelineView.Content-Scanner-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/videoRoomTimelineView.Content-Scanner-iPhone-en-GB.png new file mode 100644 index 000000000..c629d0853 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/videoRoomTimelineView.Content-Scanner-iPhone-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:300c06ba36a0bdd6e54e569dc969d32c3c0ec3bef6074542b0d4287bb441622d +size 56189 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/videoRoomTimelineView.Content-Scanner-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/videoRoomTimelineView.Content-Scanner-iPhone-pseudo.png new file mode 100644 index 000000000..825691a6e --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/videoRoomTimelineView.Content-Scanner-iPhone-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:138c4afce0d265e31e7ad46f0ceb3a0ef0c69ea62577af0c6b714a838727ad81 +size 66911 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/voiceMessageRoomTimelineView.Content-Scanner-iPad-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/voiceMessageRoomTimelineView.Content-Scanner-iPad-en-GB.png new file mode 100644 index 000000000..ed72a1fc3 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/voiceMessageRoomTimelineView.Content-Scanner-iPad-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:627f65806c9f2a8b4a81b35c3d1abde1e5f87de3d88cbd2c82fc65a2475a701b +size 99605 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/voiceMessageRoomTimelineView.Content-Scanner-iPad-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/voiceMessageRoomTimelineView.Content-Scanner-iPad-pseudo.png new file mode 100644 index 000000000..f167cda35 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/voiceMessageRoomTimelineView.Content-Scanner-iPad-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dff6c0041d79e8c2a21ee363163761cc851b8f427516c3bfeb83a322e25a704c +size 111051 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/voiceMessageRoomTimelineView.Content-Scanner-iPhone-en-GB.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/voiceMessageRoomTimelineView.Content-Scanner-iPhone-en-GB.png new file mode 100644 index 000000000..fd72d5bbb --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/voiceMessageRoomTimelineView.Content-Scanner-iPhone-en-GB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f7330ef6fcdf672e4880b84286eb369303de4590f2aa99a198193fb3bae1dd90 +size 56283 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/voiceMessageRoomTimelineView.Content-Scanner-iPhone-pseudo.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/voiceMessageRoomTimelineView.Content-Scanner-iPhone-pseudo.png new file mode 100644 index 000000000..6a32915d3 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/voiceMessageRoomTimelineView.Content-Scanner-iPhone-pseudo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b5038008c1a41a73b548990ec5e0bc8ba9147ae5f4a666242eb81fc965d841b +size 64558 diff --git a/compound-ios/Sources/Compound/Icons/CompoundIcon.swift b/compound-ios/Sources/Compound/Icons/CompoundIcon.swift index 06b0f3159..b55bd0f0d 100644 --- a/compound-ios/Sources/Compound/Icons/CompoundIcon.swift +++ b/compound-ios/Sources/Compound/Icons/CompoundIcon.swift @@ -27,7 +27,7 @@ public struct CompoundIcon: View { /// A custom icon size. case custom(CGFloat) - var value: CGFloat { + public var value: CGFloat { switch self { case .xSmall: return 16 case .small: return 20