diff --git a/ElementX/Resources/Localizations/en.lproj/Localizable.strings b/ElementX/Resources/Localizations/en.lproj/Localizable.strings index f592805304..413594832b 100644 --- a/ElementX/Resources/Localizations/en.lproj/Localizable.strings +++ b/ElementX/Resources/Localizations/en.lproj/Localizable.strings @@ -41,6 +41,7 @@ "action_create" = "Create"; "action_create_a_room" = "Create a room"; "action_deactivate" = "Deactivate"; +"action_deactivate_account" = "Deactivate account"; "action_decline" = "Decline"; "action_delete_poll" = "Delete Poll"; "action_disable" = "Disable"; @@ -64,6 +65,7 @@ "action_leave" = "Leave"; "action_leave_conversation" = "Leave conversation"; "action_leave_room" = "Leave room"; +"action_load_more" = "Load more"; "action_manage_account" = "Manage account"; "action_manage_devices" = "Manage devices"; "action_message" = "Message"; @@ -93,6 +95,7 @@ "action_send_message" = "Send message"; "action_share" = "Share"; "action_share_link" = "Share link"; +"action_show" = "Show"; "action_sign_in_again" = "Sign in again"; "action_signout" = "Sign out"; "action_signout_anyway" = "Sign out anyway"; @@ -108,8 +111,6 @@ "action_view_in_timeline" = "View in timeline"; "action_view_source" = "View source"; "action_yes" = "Yes"; -"action.load_more" = "Load more"; -"action_deactivate_account" = "Deactivate account"; "banner_migrate_to_native_sliding_sync_action" = "Log Out & Upgrade"; "banner_migrate_to_native_sliding_sync_description" = "Your server now supports a new, faster protocol. Log out and log back in to upgrade now. Doing this now will help you avoid a forced logout when the old protocol is removed later."; "banner_migrate_to_native_sliding_sync_force_logout_title" = "Your homeserver no longer supports the old protocol. Please log out and log back in to continue using the app."; diff --git a/ElementX/Sources/Application/AppSettings.swift b/ElementX/Sources/Application/AppSettings.swift index 3f8db72047..542d58057a 100644 --- a/ElementX/Sources/Application/AppSettings.swift +++ b/ElementX/Sources/Application/AppSettings.swift @@ -12,6 +12,7 @@ import SwiftUI protocol CommonSettingsProtocol { var logLevel: TracingConfiguration.LogLevel { get } var enableOnlySignedDeviceIsolationMode: Bool { get } + var hideTimelineMedia: Bool { get } } /// Store Element specific app settings. @@ -34,6 +35,7 @@ final class AppSettings { case appAppearance case sharePresence case hideUnreadMessagesBadge + case hideTimelineMedia case elementCallBaseURLOverride case elementCallEncryptionEnabled @@ -285,6 +287,9 @@ final class AppSettings { /// Configuration to enable only signed device isolation mode for crypto. In this mode only devices signed by their owner will be considered in e2ee rooms. @UserPreference(key: UserDefaultsKeys.enableOnlySignedDeviceIsolationMode, defaultValue: false, storageType: .userDefaults(store)) var enableOnlySignedDeviceIsolationMode + + @UserPreference(key: UserDefaultsKeys.hideTimelineMedia, defaultValue: false, storageType: .userDefaults(store)) + var hideTimelineMedia } extension AppSettings: CommonSettingsProtocol { } diff --git a/ElementX/Sources/Generated/Strings.swift b/ElementX/Sources/Generated/Strings.swift index 29098b6e85..55bb4b85db 100644 --- a/ElementX/Sources/Generated/Strings.swift +++ b/ElementX/Sources/Generated/Strings.swift @@ -164,6 +164,8 @@ internal enum L10n { internal static var actionLeaveConversation: String { return L10n.tr("Localizable", "action_leave_conversation") } /// Leave room internal static var actionLeaveRoom: String { return L10n.tr("Localizable", "action_leave_room") } + /// Load more + internal static var actionLoadMore: String { return L10n.tr("Localizable", "action_load_more") } /// Manage account internal static var actionManageAccount: String { return L10n.tr("Localizable", "action_manage_account") } /// Manage devices @@ -222,6 +224,8 @@ internal enum L10n { internal static var actionShare: String { return L10n.tr("Localizable", "action_share") } /// Share link internal static var actionShareLink: String { return L10n.tr("Localizable", "action_share_link") } + /// Show + internal static var actionShow: String { return L10n.tr("Localizable", "action_show") } /// Sign in again internal static var actionSignInAgain: String { return L10n.tr("Localizable", "action_sign_in_again") } /// Sign out @@ -2396,11 +2400,6 @@ internal enum L10n { /// Check UnifiedPush internal static var troubleshootNotificationsTestUnifiedPushTitle: String { return L10n.tr("Localizable", "troubleshoot_notifications_test_unified_push_title") } - internal enum Action { - /// Load more - internal static var loadMore: String { return L10n.tr("Localizable", "action.load_more") } - } - internal enum Banner { internal enum SetUpRecovery { /// Generate a new recovery key that can be used to restore your encrypted message history in case you lose access to your devices. diff --git a/ElementX/Sources/Other/SwiftUI/Views/LoadableAvatarImage.swift b/ElementX/Sources/Other/SwiftUI/Views/LoadableAvatarImage.swift index 2c35c9a5e9..7daf4655de 100644 --- a/ElementX/Sources/Other/SwiftUI/Views/LoadableAvatarImage.swift +++ b/ElementX/Sources/Other/SwiftUI/Views/LoadableAvatarImage.swift @@ -50,6 +50,7 @@ struct LoadableAvatarImage: View { .frame(width: frameSize, height: frameSize) .background(Color.compound.bgCanvasDefault) .clipShape(Circle()) + .environment(\.shouldAutomaticallyLoadImages, true) // We always load avatars. } @ViewBuilder diff --git a/ElementX/Sources/Other/SwiftUI/Views/LoadableImage.swift b/ElementX/Sources/Other/SwiftUI/Views/LoadableImage.swift index d1183ca400..6bf098b997 100644 --- a/ElementX/Sources/Other/SwiftUI/Views/LoadableImage.swift +++ b/ElementX/Sources/Other/SwiftUI/Views/LoadableImage.swift @@ -6,12 +6,17 @@ // import Combine +import Compound import Kingfisher import SwiftUI /// Used to configure animations enum LoadableImageMediaType { + /// An avatar (can be displayed anywhere within the app). case avatar + /// An image displayed in the timeline. + case timelineItem + /// Any other media (can be displayed anywhere within the app). case generic } @@ -79,6 +84,8 @@ struct LoadableImage: View { } private struct LoadableImageContent: View, ImageDataProvider { + @Environment(\.shouldAutomaticallyLoadImages) private var loadAutomatically + private let mediaSource: MediaSourceProxy private let mediaType: LoadableImageMediaType private let blurhash: String? @@ -86,6 +93,7 @@ private struct LoadableImageContent PlaceholderView @StateObject private var contentLoader: ContentLoader + @State private var loadManually = false init(mediaSource: MediaSourceProxy, mediaType: LoadableImageMediaType, @@ -104,36 +112,40 @@ private struct LoadableImageContent some View { Color.compound._bgBubbleIncoming } + static func transformer(_ view: AnyView) -> some View { + view.overlay { + Image(systemSymbol: .playCircleFill) + .font(.largeTitle) + .foregroundStyle(.compound.iconAccentPrimary) + } + } + + static func makeMediaProvider(isLoading: Bool = false) -> MediaProviderProtocol { + let mediaProvider = MediaProviderMock(configuration: .init()) + + if isLoading { + mediaProvider.imageFromSourceSizeClosure = { _, _ in nil } + mediaProvider.loadFileFromSourceBodyClosure = { _, _ in .failure(.failedRetrievingFile) } + mediaProvider.loadImageDataFromSourceClosure = { _ in .failure(.failedRetrievingImage) } + mediaProvider.loadImageFromSourceSizeClosure = { _, _ in .failure(.failedRetrievingImage) } + mediaProvider.loadThumbnailForSourceSourceSizeClosure = { _, _ in .failure(.failedRetrievingThumbnail) } + mediaProvider.loadImageRetryingOnReconnectionSizeClosure = { _, _ in + Task { throw MediaProviderError.failedRetrievingImage } + } + } + return mediaProvider + } +} + +private extension View { + func layout(title: String, hideTimelineMedia: Bool = false) -> some View { + aspectRatio(contentMode: .fit) + .clipShape(RoundedRectangle(cornerRadius: 20)) + .overlay(alignment: .bottom) { + Text(title) + .font(.caption2) + .offset(y: 16) + .padding(.horizontal, -5) + } + .environment(\.shouldAutomaticallyLoadImages, !hideTimelineMedia) + } +} diff --git a/ElementX/Sources/Screens/RoomPollsHistoryScreen/View/RoomPollsHistoryScreen.swift b/ElementX/Sources/Screens/RoomPollsHistoryScreen/View/RoomPollsHistoryScreen.swift index 9358e02270..96d4c8c548 100644 --- a/ElementX/Sources/Screens/RoomPollsHistoryScreen/View/RoomPollsHistoryScreen.swift +++ b/ElementX/Sources/Screens/RoomPollsHistoryScreen/View/RoomPollsHistoryScreen.swift @@ -91,7 +91,7 @@ struct RoomPollsHistoryScreen: View { Button { context.send(viewAction: .loadMore) } label: { - Text(L10n.Action.loadMore) + Text(L10n.actionLoadMore) .font(.compound.bodyLGSemibold) .padding(.horizontal, 12) } diff --git a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift index 827e7bb229..dee0516fb5 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift @@ -42,6 +42,8 @@ struct RoomScreen: View { .background(Color.compound.bgCanvasDefault.ignoresSafeArea()) .environmentObject(timelineContext) .environment(\.timelineContext, timelineContext) + // Make sure the reply header honours the hideTimelineMedia setting too. + .environment(\.shouldAutomaticallyLoadImages, !timelineContext.viewState.hideTimelineMedia) } .overlay(alignment: .top) { Group { diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift index 15b1f7d682..d2f9f7a4b4 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift @@ -44,9 +44,10 @@ protocol DeveloperOptionsProtocol: AnyObject { var logLevel: TracingConfiguration.LogLevel { get set } var slidingSyncDiscovery: AppSettings.SlidingSyncDiscovery { get set } var hideUnreadMessagesBadge: Bool { get set } - var elementCallBaseURLOverride: URL? { get set } var fuzzyRoomListSearchEnabled: Bool { get set } + var hideTimelineMedia: Bool { get set } var enableOnlySignedDeviceIsolationMode: Bool { get set } + var elementCallBaseURLOverride: URL? { get set } } extension AppSettings: DeveloperOptionsProtocol { } diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift index 723ae359d5..683b0a1f1b 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift @@ -45,6 +45,12 @@ struct DeveloperOptionsScreen: View { } } + Section("Timeline") { + Toggle(isOn: $context.hideTimelineMedia) { + Text("Hide image & video previews") + } + } + Section { Toggle(isOn: $context.enableOnlySignedDeviceIsolationMode) { Text("Exclude not secure devices when sending/receiving messages") diff --git a/ElementX/Sources/Screens/Timeline/TimelineModels.swift b/ElementX/Sources/Screens/Timeline/TimelineModels.swift index ab72290152..6bfcc9b9ca 100644 --- a/ElementX/Sources/Screens/Timeline/TimelineModels.swift +++ b/ElementX/Sources/Screens/Timeline/TimelineModels.swift @@ -97,6 +97,7 @@ struct TimelineViewState: BindableState { var canCurrentUserRedactSelf = false var canCurrentUserPin = false var isViewSourceEnabled: Bool + var hideTimelineMedia: Bool // The `pinnedEventIDs` are used only to determine if an item is already pinned or not. // It's updated from the room info, so it's faster than using the timeline diff --git a/ElementX/Sources/Screens/Timeline/TimelineTableViewController.swift b/ElementX/Sources/Screens/Timeline/TimelineTableViewController.swift index 6b32e9c74c..8876749e8f 100644 --- a/ElementX/Sources/Screens/Timeline/TimelineTableViewController.swift +++ b/ElementX/Sources/Screens/Timeline/TimelineTableViewController.swift @@ -125,6 +125,13 @@ class TimelineTableViewController: UIViewController { } } + var hideTimelineMedia = false { + didSet { + guard let snapshot = dataSource?.snapshot() else { return } + dataSource?.applySnapshotUsingReloadData(snapshot) + } + } + /// Used to hold an observable object that the typing indicator can use let typingMembers = TypingMembersObservableObject(members: []) @@ -260,21 +267,19 @@ class TimelineTableViewController: UIViewController { let cell = tableView.dequeueReusableCell(withIdentifier: TimelineItemCell.reuseIdentifier, for: indexPath) guard let self, let cell = cell as? TimelineItemCell else { return cell } - // A local reference to avoid capturing self in the cell configuration. - let coordinator = self.coordinator - let viewState = timelineItemsDictionary[id] cell.item = viewState guard let viewState else { return cell } - cell.contentConfiguration = UIHostingConfiguration { + cell.contentConfiguration = UIHostingConfiguration { [coordinator, hideTimelineMedia] in RoomTimelineItemView(viewState: viewState) .id(id) .frame(maxWidth: .infinity, alignment: .leading) .environmentObject(coordinator.context) // Attempted fix at a crash in TimelineItemContextMenu .environment(\.timelineContext, coordinator.context) + .environment(\.shouldAutomaticallyLoadImages, !hideTimelineMedia) } .margins(.all, 0) // Margins are handled in the stylers .minSize(height: 1) diff --git a/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift b/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift index 0b01621447..2dc59055fa 100644 --- a/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift +++ b/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift @@ -78,6 +78,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol { timelineViewState: TimelineState(focussedEvent: focussedEventID.map { .init(eventID: $0, appearance: .immediate) }), ownUserID: roomProxy.ownUserID, isViewSourceEnabled: appSettings.viewSourceEnabled, + hideTimelineMedia: appSettings.hideTimelineMedia, bindings: .init(reactionsCollapsed: [:])), mediaProvider: mediaProvider) @@ -440,6 +441,10 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol { appSettings.$viewSourceEnabled .weakAssign(to: \.state.isViewSourceEnabled, on: self) .store(in: &cancellables) + + appSettings.$hideTimelineMedia + .weakAssign(to: \.state.hideTimelineMedia, on: self) + .store(in: &cancellables) } private func updatePinnedEventIDs() async { diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/ImageRoomTimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/ImageRoomTimelineView.swift index b872a84973..c213ead505 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/ImageRoomTimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/ImageRoomTimelineView.swift @@ -15,6 +15,7 @@ struct ImageRoomTimelineView: View { var body: some View { TimelineStyler(timelineItem: timelineItem) { LoadableImage(mediaSource: source, + mediaType: .timelineItem, blurhash: timelineItem.content.blurhash, mediaProvider: context.mediaProvider) { placeholder @@ -35,14 +36,9 @@ struct ImageRoomTimelineView: View { } var placeholder: some View { - ZStack { - Rectangle() - .foregroundColor(timelineItem.isOutgoing ? .compound._bgBubbleOutgoing : .compound._bgBubbleIncoming) - .opacity(0.3) - - ProgressView(L10n.commonLoading) - .frame(maxWidth: .infinity) - } + Rectangle() + .foregroundColor(timelineItem.isOutgoing ? .compound._bgBubbleOutgoing : .compound._bgBubbleIncoming) + .opacity(0.3) } } diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/StickerRoomTimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/StickerRoomTimelineView.swift index 720d5c49f2..2d9a17f3c8 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/StickerRoomTimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/StickerRoomTimelineView.swift @@ -15,6 +15,7 @@ struct StickerRoomTimelineView: View { var body: some View { TimelineStyler(timelineItem: timelineItem) { LoadableImage(url: timelineItem.imageURL, + mediaType: .timelineItem, blurhash: timelineItem.blurhash, mediaProvider: context.mediaProvider) { placeholder @@ -27,14 +28,9 @@ struct StickerRoomTimelineView: View { } private var placeholder: some View { - ZStack { - Rectangle() - .foregroundColor(timelineItem.isOutgoing ? .compound._bgBubbleOutgoing : .compound._bgBubbleIncoming) - .opacity(0.3) - - ProgressView(L10n.commonLoading) - .frame(maxWidth: .infinity) - } + Rectangle() + .foregroundColor(timelineItem.isOutgoing ? .compound._bgBubbleOutgoing : .compound._bgBubbleIncoming) + .opacity(0.3) } } diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/VideoRoomTimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/VideoRoomTimelineView.swift index c681f0e3be..23bc73d589 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/VideoRoomTimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/VideoRoomTimelineView.swift @@ -26,6 +26,7 @@ struct VideoRoomTimelineView: View { var thumbnail: some View { if let thumbnailSource = timelineItem.content.thumbnailSource { LoadableImage(mediaSource: thumbnailSource, + mediaType: .timelineItem, blurhash: timelineItem.content.blurhash, mediaProvider: context.mediaProvider) { imageView in imageView @@ -47,14 +48,9 @@ struct VideoRoomTimelineView: View { } var placeholder: some View { - ZStack { - Rectangle() - .foregroundColor(timelineItem.isOutgoing ? .compound._bgBubbleOutgoing : .compound._bgBubbleIncoming) - .opacity(0.3) - - ProgressView(L10n.commonLoading) - .frame(maxWidth: .infinity) - } + Rectangle() + .foregroundColor(timelineItem.isOutgoing ? .compound._bgBubbleOutgoing : .compound._bgBubbleIncoming) + .opacity(0.3) } } diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineView.swift index 1090dbc56c..c9133b242e 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineView.swift @@ -60,6 +60,9 @@ struct TimelineView: UIViewControllerRepresentable { if tableViewController.focussedEvent != context.viewState.timelineViewState.focussedEvent { tableViewController.focussedEvent = context.viewState.timelineViewState.focussedEvent } + if tableViewController.hideTimelineMedia != context.viewState.hideTimelineMedia { + tableViewController.hideTimelineMedia = context.viewState.hideTimelineMedia + } if tableViewController.typingMembers.members != context.viewState.typingMembers { tableViewController.setTypingMembers(context.viewState.typingMembers) diff --git a/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift b/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift index 62cad3cd3f..820e880b81 100644 --- a/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift +++ b/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift @@ -247,6 +247,40 @@ enum RoomTimelineItemFixtures { senderDisplayName: index > 10 ? "Alice" : "Bob") } } + + static var mediaChunk: [RoomTimelineItemProtocol] { + [ + VideoRoomTimelineItem(id: .random, + timestamp: "10:47 am", + isOutgoing: false, + isEditable: false, + canBeRepliedTo: true, + isThreaded: false, + sender: .init(id: ""), + content: .init(body: "video", + duration: 100, + source: .init(url: .picturesDirectory, mimeType: nil), + thumbnailSource: .init(url: .picturesDirectory, mimeType: nil), + width: 1920, + height: 1080, + aspectRatio: 1.78, + blurhash: "KtI~70X5V?yss9oyrYs:t6")), + ImageRoomTimelineItem(id: .random, + timestamp: "10:47 am", + isOutgoing: false, + isEditable: false, + canBeRepliedTo: true, + isThreaded: false, + sender: .init(id: ""), + content: .init(body: "image", + source: .init(url: .picturesDirectory, mimeType: nil), + thumbnailSource: nil, + width: 5120, + height: 3412, + aspectRatio: 1.5, + blurhash: "KpE4oyayR5|GbHb];3j@of")) + ] + } } private extension TextRoomTimelineItem { @@ -260,9 +294,7 @@ private extension TextRoomTimelineItem { sender: .init(id: "", displayName: senderDisplayName), content: .init(body: text)) } -} - -private extension TextRoomTimelineItem { + func withReadReceipts(_ receipts: [ReadReceipt]) -> TextRoomTimelineItem { var newSelf = self newSelf.properties.orderedReadReceipts = receipts diff --git a/NSE/Sources/NotificationContentBuilder.swift b/NSE/Sources/NotificationContentBuilder.swift index ad01691b4d..f767a9538d 100644 --- a/NSE/Sources/NotificationContentBuilder.swift +++ b/NSE/Sources/NotificationContentBuilder.swift @@ -11,6 +11,7 @@ import UserNotifications struct NotificationContentBuilder { let messageEventStringBuilder: RoomMessageEventStringBuilder + let settings: CommonSettingsProtocol /// Process the given notification item proxy /// - Parameters: @@ -100,6 +101,8 @@ struct NotificationContentBuilder { let displayName = notificationItem.senderDisplayName ?? notificationItem.roomDisplayName notification.body = String(messageEventStringBuilder.buildAttributedString(for: messageType, senderDisplayName: displayName).characters) + guard !settings.hideTimelineMedia else { return notification } + switch messageType { case .image(content: let content): notification = await notification.addMediaAttachment(using: mediaProvider, diff --git a/NSE/Sources/NotificationServiceExtension.swift b/NSE/Sources/NotificationServiceExtension.swift index 5443c1ffc3..3ea1ed8a6c 100644 --- a/NSE/Sources/NotificationServiceExtension.swift +++ b/NSE/Sources/NotificationServiceExtension.swift @@ -33,7 +33,8 @@ import UserNotifications // database, logging, etc. are only ever setup once per *process* private let settings: CommonSettingsProtocol = AppSettings() -private let notificationContentBuilder = NotificationContentBuilder(messageEventStringBuilder: RoomMessageEventStringBuilder(attributedStringBuilder: AttributedStringBuilder(mentionBuilder: PlainMentionBuilder()), prefix: .none)) +private let notificationContentBuilder = NotificationContentBuilder(messageEventStringBuilder: RoomMessageEventStringBuilder(attributedStringBuilder: AttributedStringBuilder(mentionBuilder: PlainMentionBuilder()), prefix: .none), + settings: settings) private let keychainController = KeychainController(service: .sessions, accessGroup: InfoPlistReader.main.keychainAccessGroupIdentifier) diff --git a/PreviewTests/Sources/GeneratedPreviewTests.swift b/PreviewTests/Sources/GeneratedPreviewTests.swift index 47fb1f5988..5819975f1c 100644 --- a/PreviewTests/Sources/GeneratedPreviewTests.swift +++ b/PreviewTests/Sources/GeneratedPreviewTests.swift @@ -311,6 +311,12 @@ extension PreviewTests { } } + func test_loadableImage() { + for preview in LoadableImage_Previews._allPreviews { + assertSnapshots(matching: preview) + } + } + func test_locationMarkerView() { for preview in LocationMarkerView_Previews._allPreviews { assertSnapshots(matching: preview) diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_loadableImage-iPad-en-GB.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_loadableImage-iPad-en-GB.1.png new file mode 100644 index 0000000000..a6d91419e9 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_loadableImage-iPad-en-GB.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d29fa1a636ab74e34bed4d8155c5801a586e1d93c7fb3da736df99941d0b374 +size 195721 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_loadableImage-iPad-pseudo.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_loadableImage-iPad-pseudo.1.png new file mode 100644 index 0000000000..c9ad057634 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_loadableImage-iPad-pseudo.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dcc87dd75498a5eaf4033d3630228ee8998b25832714a3fb7586d421cd4a9600 +size 209012 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_loadableImage-iPhone-16-en-GB.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_loadableImage-iPhone-16-en-GB.1.png new file mode 100644 index 0000000000..a75077b460 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_loadableImage-iPhone-16-en-GB.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b6a5bcecdde9ae09281b3a14fe74640bdc3a49b00453394efa37ceb943ca9ef0 +size 182115 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_loadableImage-iPhone-16-pseudo.1.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_loadableImage-iPhone-16-pseudo.1.png new file mode 100644 index 0000000000..e8634a78ec --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_loadableImage-iPhone-16-pseudo.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:185a048522753d4c5175d6bd35ec98e463aeec8b4674b0e5ad7c2ef20a683001 +size 195492