From 6e51969d27bf0c9240d389e32f517e48849ca23f Mon Sep 17 00:00:00 2001 From: Doug <6060466+pixlwave@users.noreply.github.com> Date: Tue, 8 Oct 2024 11:00:58 +0100 Subject: [PATCH] Update the SDK and use media `filename` and `caption` internally. (#3375) Doesn't render captions (other than in fallback places). --- ElementX.xcodeproj/project.pbxproj | 5 + ElementX/Sources/Mocks/ClientProxyMock.swift | 2 +- .../Mocks/Generated/GeneratedMocks.swift | 152 ++++----- .../Mocks/Generated/SDKGeneratedMocks.swift | 191 +++++++++++ .../Sources/Mocks/MediaProviderMock.swift | 2 +- .../Other/SwiftUI/Views/LoadableImage.swift | 2 +- .../View/MessageComposer.swift | 19 +- .../Timeline/TimelineInteractionHandler.swift | 19 +- .../View/Replies/TimelineReplyView.swift | 31 +- .../Style/TimelineItemBubbledStylerView.swift | 321 +++++++----------- .../AudioRoomTimelineView.swift | 6 +- .../FileRoomTimelineView.swift | 15 +- .../ImageRoomTimelineView.swift | 10 +- .../VideoRoomTimelineView.swift | 17 +- .../Sources/Services/Client/ClientProxy.swift | 4 +- .../Services/Media/Provider/MediaLoader.swift | 4 +- .../Media/Provider/MediaLoaderProtocol.swift | 2 +- .../Media/Provider/MediaProvider.swift | 4 +- .../Provider/MediaProviderProtocol.swift | 4 +- .../Fixtures/RoomTimelineItemFixtures.swift | 4 +- .../Messages/AudioRoomTimelineItem.swift | 2 +- .../AudioRoomTimelineItemContent.swift | 6 +- .../Items/Messages/FileRoomTimelineItem.swift | 2 +- .../FileRoomTimelineItemContent.swift | 6 +- .../Messages/ImageRoomTimelineItem.swift | 2 +- .../ImageRoomTimelineItemContent.swift | 6 +- .../Messages/VideoRoomTimelineItem.swift | 2 +- .../VideoRoomTimelineItemContent.swift | 6 +- .../VoiceMessageRoomTimelineItem.swift | 2 +- .../VoiceMessageRoomTimelineView.swift | 2 +- .../RoomTimelineItemFactory.swift | 32 +- .../VoiceMessageMediaManager.swift | 2 +- ...Composer-iPad-en-GB.Replying-in-thread.png | 4 +- ...st_messageComposer-iPad-en-GB.Replying.png | 4 +- ...omposer-iPad-pseudo.Replying-in-thread.png | 4 +- ...t_messageComposer-iPad-pseudo.Replying.png | 4 +- ...ser-iPhone-16-en-GB.Replying-in-thread.png | 4 +- ...ssageComposer-iPhone-16-en-GB.Replying.png | 4 +- ...er-iPhone-16-pseudo.Replying-in-thread.png | 4 +- ...sageComposer-iPhone-16-pseudo.Replying.png | 4 +- UnitTests/Sources/LoggingTests.swift | 17 +- .../VoiceMessageMediaManagerTests.swift | 6 +- 42 files changed, 581 insertions(+), 358 deletions(-) diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 38c2216c80..df1d323860 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -1921,6 +1921,7 @@ A84D413BF49F0E980F010A6B /* LogViewerScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewerScreenCoordinator.swift; sourceTree = ""; }; A861DA5932B128FE1DCB5CE2 /* InviteUsersScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenCoordinator.swift; sourceTree = ""; }; A8DF55467ED4CE76B7AE9A33 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/InfoPlist.strings; sourceTree = ""; }; + A9873374E72AA53260AE90A2 /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fa; path = fa.lproj/Localizable.strings; sourceTree = ""; }; A9B069D7772DDF6513E0F1B8 /* AuthenticationFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationFlowCoordinator.swift; sourceTree = ""; }; A9E6065FC6BC4A1B4C629E08 /* TimelineItemMenuActionProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemMenuActionProvider.swift; sourceTree = ""; }; A9FAFE1C2149E6AC8156ED2B /* Collection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Collection.swift; sourceTree = ""; }; @@ -2065,6 +2066,7 @@ C6A9F49B3EE59147AF2F70BB /* SeparatorRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorRoomTimelineItem.swift; sourceTree = ""; }; C6FEA87EA3752203065ECE27 /* BugReportUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportUITests.swift; sourceTree = ""; }; C705E605EF57C19DBE86FFA1 /* PlaceholderAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderAvatarImage.swift; sourceTree = ""; }; + C715CFE00686DACA59D836EA /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fa; path = fa.lproj/SAS.strings; sourceTree = ""; }; C729D95CB4588D4D9AAC3DFA /* RoomChangePermissionsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangePermissionsScreenModels.swift; sourceTree = ""; }; C733D11B421CFE3A657EF230 /* test_image.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = test_image.png; sourceTree = ""; }; C75EF87651B00A176AB08E97 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -5755,6 +5757,7 @@ en, es, et, + fa, fr, hu, id, @@ -7119,6 +7122,7 @@ 7447C0AD7EF302CD027D6230 /* en */, 6722709BD6178E10B70C9641 /* es */, F3C7252B3461D06175D975A4 /* et */, + C715CFE00686DACA59D836EA /* fa */, CEE20623EB4A9B88FB29F2BA /* fr */, D196116D2DD3F2757D45FCB7 /* hu */, 330AF4D121C3396F7A14B21D /* id */, @@ -7178,6 +7182,7 @@ CACA846B3E3E9A521D98B178 /* en */, CBBCC6E74774E79B599625D0 /* es */, A443FAE2EE820A5790C35C8D /* et */, + A9873374E72AA53260AE90A2 /* fa */, CC680E0E79D818706CB28CF8 /* fr */, 624244C398804ADC885239AA /* hu */, EF98A02DED04075F7CF0C721 /* id */, diff --git a/ElementX/Sources/Mocks/ClientProxyMock.swift b/ElementX/Sources/Mocks/ClientProxyMock.swift index e727ba6af3..825588e479 100644 --- a/ElementX/Sources/Mocks/ClientProxyMock.swift +++ b/ElementX/Sources/Mocks/ClientProxyMock.swift @@ -77,7 +77,7 @@ extension ClientProxyMock { loadMediaContentForSourceThrowableError = ClientProxyError.sdkError(ClientProxyMockError.generic) loadMediaThumbnailForSourceWidthHeightThrowableError = ClientProxyError.sdkError(ClientProxyMockError.generic) - loadMediaFileForSourceBodyThrowableError = ClientProxyError.sdkError(ClientProxyMockError.generic) + loadMediaFileForSourceFilenameThrowableError = ClientProxyError.sdkError(ClientProxyMockError.generic) secureBackupController = { let secureBackupController = SecureBackupControllerMock() diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index e4f2dadf38..c9f2da6433 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -4710,16 +4710,16 @@ class ClientProxyMock: ClientProxyProtocol { } //MARK: - loadMediaFileForSource - var loadMediaFileForSourceBodyThrowableError: Error? - var loadMediaFileForSourceBodyUnderlyingCallsCount = 0 - var loadMediaFileForSourceBodyCallsCount: Int { + var loadMediaFileForSourceFilenameThrowableError: Error? + var loadMediaFileForSourceFilenameUnderlyingCallsCount = 0 + var loadMediaFileForSourceFilenameCallsCount: Int { get { if Thread.isMainThread { - return loadMediaFileForSourceBodyUnderlyingCallsCount + return loadMediaFileForSourceFilenameUnderlyingCallsCount } else { var returnValue: Int? = nil DispatchQueue.main.sync { - returnValue = loadMediaFileForSourceBodyUnderlyingCallsCount + returnValue = loadMediaFileForSourceFilenameUnderlyingCallsCount } return returnValue! @@ -4727,29 +4727,29 @@ class ClientProxyMock: ClientProxyProtocol { } set { if Thread.isMainThread { - loadMediaFileForSourceBodyUnderlyingCallsCount = newValue + loadMediaFileForSourceFilenameUnderlyingCallsCount = newValue } else { DispatchQueue.main.sync { - loadMediaFileForSourceBodyUnderlyingCallsCount = newValue + loadMediaFileForSourceFilenameUnderlyingCallsCount = newValue } } } } - var loadMediaFileForSourceBodyCalled: Bool { - return loadMediaFileForSourceBodyCallsCount > 0 + var loadMediaFileForSourceFilenameCalled: Bool { + return loadMediaFileForSourceFilenameCallsCount > 0 } - var loadMediaFileForSourceBodyReceivedArguments: (source: MediaSourceProxy, body: String?)? - var loadMediaFileForSourceBodyReceivedInvocations: [(source: MediaSourceProxy, body: String?)] = [] + var loadMediaFileForSourceFilenameReceivedArguments: (source: MediaSourceProxy, filename: String?)? + var loadMediaFileForSourceFilenameReceivedInvocations: [(source: MediaSourceProxy, filename: String?)] = [] - var loadMediaFileForSourceBodyUnderlyingReturnValue: MediaFileHandleProxy! - var loadMediaFileForSourceBodyReturnValue: MediaFileHandleProxy! { + var loadMediaFileForSourceFilenameUnderlyingReturnValue: MediaFileHandleProxy! + var loadMediaFileForSourceFilenameReturnValue: MediaFileHandleProxy! { get { if Thread.isMainThread { - return loadMediaFileForSourceBodyUnderlyingReturnValue + return loadMediaFileForSourceFilenameUnderlyingReturnValue } else { var returnValue: MediaFileHandleProxy? = nil DispatchQueue.main.sync { - returnValue = loadMediaFileForSourceBodyUnderlyingReturnValue + returnValue = loadMediaFileForSourceFilenameUnderlyingReturnValue } return returnValue! @@ -4757,29 +4757,29 @@ class ClientProxyMock: ClientProxyProtocol { } set { if Thread.isMainThread { - loadMediaFileForSourceBodyUnderlyingReturnValue = newValue + loadMediaFileForSourceFilenameUnderlyingReturnValue = newValue } else { DispatchQueue.main.sync { - loadMediaFileForSourceBodyUnderlyingReturnValue = newValue + loadMediaFileForSourceFilenameUnderlyingReturnValue = newValue } } } } - var loadMediaFileForSourceBodyClosure: ((MediaSourceProxy, String?) async throws -> MediaFileHandleProxy)? + var loadMediaFileForSourceFilenameClosure: ((MediaSourceProxy, String?) async throws -> MediaFileHandleProxy)? - func loadMediaFileForSource(_ source: MediaSourceProxy, body: String?) async throws -> MediaFileHandleProxy { - if let error = loadMediaFileForSourceBodyThrowableError { + func loadMediaFileForSource(_ source: MediaSourceProxy, filename: String?) async throws -> MediaFileHandleProxy { + if let error = loadMediaFileForSourceFilenameThrowableError { throw error } - loadMediaFileForSourceBodyCallsCount += 1 - loadMediaFileForSourceBodyReceivedArguments = (source: source, body: body) + loadMediaFileForSourceFilenameCallsCount += 1 + loadMediaFileForSourceFilenameReceivedArguments = (source: source, filename: filename) DispatchQueue.main.async { - self.loadMediaFileForSourceBodyReceivedInvocations.append((source: source, body: body)) + self.loadMediaFileForSourceFilenameReceivedInvocations.append((source: source, filename: filename)) } - if let loadMediaFileForSourceBodyClosure = loadMediaFileForSourceBodyClosure { - return try await loadMediaFileForSourceBodyClosure(source, body) + if let loadMediaFileForSourceFilenameClosure = loadMediaFileForSourceFilenameClosure { + return try await loadMediaFileForSourceFilenameClosure(source, filename) } else { - return loadMediaFileForSourceBodyReturnValue + return loadMediaFileForSourceFilenameReturnValue } } } @@ -9693,16 +9693,16 @@ class MediaLoaderMock: MediaLoaderProtocol { } //MARK: - loadMediaFileForSource - var loadMediaFileForSourceBodyThrowableError: Error? - var loadMediaFileForSourceBodyUnderlyingCallsCount = 0 - var loadMediaFileForSourceBodyCallsCount: Int { + var loadMediaFileForSourceFilenameThrowableError: Error? + var loadMediaFileForSourceFilenameUnderlyingCallsCount = 0 + var loadMediaFileForSourceFilenameCallsCount: Int { get { if Thread.isMainThread { - return loadMediaFileForSourceBodyUnderlyingCallsCount + return loadMediaFileForSourceFilenameUnderlyingCallsCount } else { var returnValue: Int? = nil DispatchQueue.main.sync { - returnValue = loadMediaFileForSourceBodyUnderlyingCallsCount + returnValue = loadMediaFileForSourceFilenameUnderlyingCallsCount } return returnValue! @@ -9710,29 +9710,29 @@ class MediaLoaderMock: MediaLoaderProtocol { } set { if Thread.isMainThread { - loadMediaFileForSourceBodyUnderlyingCallsCount = newValue + loadMediaFileForSourceFilenameUnderlyingCallsCount = newValue } else { DispatchQueue.main.sync { - loadMediaFileForSourceBodyUnderlyingCallsCount = newValue + loadMediaFileForSourceFilenameUnderlyingCallsCount = newValue } } } } - var loadMediaFileForSourceBodyCalled: Bool { - return loadMediaFileForSourceBodyCallsCount > 0 + var loadMediaFileForSourceFilenameCalled: Bool { + return loadMediaFileForSourceFilenameCallsCount > 0 } - var loadMediaFileForSourceBodyReceivedArguments: (source: MediaSourceProxy, body: String?)? - var loadMediaFileForSourceBodyReceivedInvocations: [(source: MediaSourceProxy, body: String?)] = [] + var loadMediaFileForSourceFilenameReceivedArguments: (source: MediaSourceProxy, filename: String?)? + var loadMediaFileForSourceFilenameReceivedInvocations: [(source: MediaSourceProxy, filename: String?)] = [] - var loadMediaFileForSourceBodyUnderlyingReturnValue: MediaFileHandleProxy! - var loadMediaFileForSourceBodyReturnValue: MediaFileHandleProxy! { + var loadMediaFileForSourceFilenameUnderlyingReturnValue: MediaFileHandleProxy! + var loadMediaFileForSourceFilenameReturnValue: MediaFileHandleProxy! { get { if Thread.isMainThread { - return loadMediaFileForSourceBodyUnderlyingReturnValue + return loadMediaFileForSourceFilenameUnderlyingReturnValue } else { var returnValue: MediaFileHandleProxy? = nil DispatchQueue.main.sync { - returnValue = loadMediaFileForSourceBodyUnderlyingReturnValue + returnValue = loadMediaFileForSourceFilenameUnderlyingReturnValue } return returnValue! @@ -9740,29 +9740,29 @@ class MediaLoaderMock: MediaLoaderProtocol { } set { if Thread.isMainThread { - loadMediaFileForSourceBodyUnderlyingReturnValue = newValue + loadMediaFileForSourceFilenameUnderlyingReturnValue = newValue } else { DispatchQueue.main.sync { - loadMediaFileForSourceBodyUnderlyingReturnValue = newValue + loadMediaFileForSourceFilenameUnderlyingReturnValue = newValue } } } } - var loadMediaFileForSourceBodyClosure: ((MediaSourceProxy, String?) async throws -> MediaFileHandleProxy)? + var loadMediaFileForSourceFilenameClosure: ((MediaSourceProxy, String?) async throws -> MediaFileHandleProxy)? - func loadMediaFileForSource(_ source: MediaSourceProxy, body: String?) async throws -> MediaFileHandleProxy { - if let error = loadMediaFileForSourceBodyThrowableError { + func loadMediaFileForSource(_ source: MediaSourceProxy, filename: String?) async throws -> MediaFileHandleProxy { + if let error = loadMediaFileForSourceFilenameThrowableError { throw error } - loadMediaFileForSourceBodyCallsCount += 1 - loadMediaFileForSourceBodyReceivedArguments = (source: source, body: body) + loadMediaFileForSourceFilenameCallsCount += 1 + loadMediaFileForSourceFilenameReceivedArguments = (source: source, filename: filename) DispatchQueue.main.async { - self.loadMediaFileForSourceBodyReceivedInvocations.append((source: source, body: body)) + self.loadMediaFileForSourceFilenameReceivedInvocations.append((source: source, filename: filename)) } - if let loadMediaFileForSourceBodyClosure = loadMediaFileForSourceBodyClosure { - return try await loadMediaFileForSourceBodyClosure(source, body) + if let loadMediaFileForSourceFilenameClosure = loadMediaFileForSourceFilenameClosure { + return try await loadMediaFileForSourceFilenameClosure(source, filename) } else { - return loadMediaFileForSourceBodyReturnValue + return loadMediaFileForSourceFilenameReturnValue } } } @@ -10628,15 +10628,15 @@ class MediaProviderMock: MediaProviderProtocol { } //MARK: - loadFileFromSource - var loadFileFromSourceBodyUnderlyingCallsCount = 0 - var loadFileFromSourceBodyCallsCount: Int { + var loadFileFromSourceFilenameUnderlyingCallsCount = 0 + var loadFileFromSourceFilenameCallsCount: Int { get { if Thread.isMainThread { - return loadFileFromSourceBodyUnderlyingCallsCount + return loadFileFromSourceFilenameUnderlyingCallsCount } else { var returnValue: Int? = nil DispatchQueue.main.sync { - returnValue = loadFileFromSourceBodyUnderlyingCallsCount + returnValue = loadFileFromSourceFilenameUnderlyingCallsCount } return returnValue! @@ -10644,29 +10644,29 @@ class MediaProviderMock: MediaProviderProtocol { } set { if Thread.isMainThread { - loadFileFromSourceBodyUnderlyingCallsCount = newValue + loadFileFromSourceFilenameUnderlyingCallsCount = newValue } else { DispatchQueue.main.sync { - loadFileFromSourceBodyUnderlyingCallsCount = newValue + loadFileFromSourceFilenameUnderlyingCallsCount = newValue } } } } - var loadFileFromSourceBodyCalled: Bool { - return loadFileFromSourceBodyCallsCount > 0 + var loadFileFromSourceFilenameCalled: Bool { + return loadFileFromSourceFilenameCallsCount > 0 } - var loadFileFromSourceBodyReceivedArguments: (source: MediaSourceProxy, body: String?)? - var loadFileFromSourceBodyReceivedInvocations: [(source: MediaSourceProxy, body: String?)] = [] + var loadFileFromSourceFilenameReceivedArguments: (source: MediaSourceProxy, filename: String?)? + var loadFileFromSourceFilenameReceivedInvocations: [(source: MediaSourceProxy, filename: String?)] = [] - var loadFileFromSourceBodyUnderlyingReturnValue: Result! - var loadFileFromSourceBodyReturnValue: Result! { + var loadFileFromSourceFilenameUnderlyingReturnValue: Result! + var loadFileFromSourceFilenameReturnValue: Result! { get { if Thread.isMainThread { - return loadFileFromSourceBodyUnderlyingReturnValue + return loadFileFromSourceFilenameUnderlyingReturnValue } else { var returnValue: Result? = nil DispatchQueue.main.sync { - returnValue = loadFileFromSourceBodyUnderlyingReturnValue + returnValue = loadFileFromSourceFilenameUnderlyingReturnValue } return returnValue! @@ -10674,26 +10674,26 @@ class MediaProviderMock: MediaProviderProtocol { } set { if Thread.isMainThread { - loadFileFromSourceBodyUnderlyingReturnValue = newValue + loadFileFromSourceFilenameUnderlyingReturnValue = newValue } else { DispatchQueue.main.sync { - loadFileFromSourceBodyUnderlyingReturnValue = newValue + loadFileFromSourceFilenameUnderlyingReturnValue = newValue } } } } - var loadFileFromSourceBodyClosure: ((MediaSourceProxy, String?) async -> Result)? + var loadFileFromSourceFilenameClosure: ((MediaSourceProxy, String?) async -> Result)? - func loadFileFromSource(_ source: MediaSourceProxy, body: String?) async -> Result { - loadFileFromSourceBodyCallsCount += 1 - loadFileFromSourceBodyReceivedArguments = (source: source, body: body) + func loadFileFromSource(_ source: MediaSourceProxy, filename: String?) async -> Result { + loadFileFromSourceFilenameCallsCount += 1 + loadFileFromSourceFilenameReceivedArguments = (source: source, filename: filename) DispatchQueue.main.async { - self.loadFileFromSourceBodyReceivedInvocations.append((source: source, body: body)) + self.loadFileFromSourceFilenameReceivedInvocations.append((source: source, filename: filename)) } - if let loadFileFromSourceBodyClosure = loadFileFromSourceBodyClosure { - return await loadFileFromSourceBodyClosure(source, body) + if let loadFileFromSourceFilenameClosure = loadFileFromSourceFilenameClosure { + return await loadFileFromSourceFilenameClosure(source, filename) } else { - return loadFileFromSourceBodyReturnValue + return loadFileFromSourceFilenameReturnValue } } } diff --git a/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift index 8469d7de3c..da47bc6dc2 100644 --- a/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift @@ -6139,6 +6139,81 @@ open class EncryptionSDKMock: MatrixRustSDK.Encryption { } } + //MARK: - getUserIdentity + + open var getUserIdentityUserIdThrowableError: Error? + var getUserIdentityUserIdUnderlyingCallsCount = 0 + open var getUserIdentityUserIdCallsCount: Int { + get { + if Thread.isMainThread { + return getUserIdentityUserIdUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = getUserIdentityUserIdUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + getUserIdentityUserIdUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + getUserIdentityUserIdUnderlyingCallsCount = newValue + } + } + } + } + open var getUserIdentityUserIdCalled: Bool { + return getUserIdentityUserIdCallsCount > 0 + } + open var getUserIdentityUserIdReceivedUserId: String? + open var getUserIdentityUserIdReceivedInvocations: [String] = [] + + var getUserIdentityUserIdUnderlyingReturnValue: UserIdentity? + open var getUserIdentityUserIdReturnValue: UserIdentity? { + get { + if Thread.isMainThread { + return getUserIdentityUserIdUnderlyingReturnValue + } else { + var returnValue: UserIdentity?? = nil + DispatchQueue.main.sync { + returnValue = getUserIdentityUserIdUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + getUserIdentityUserIdUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + getUserIdentityUserIdUnderlyingReturnValue = newValue + } + } + } + } + open var getUserIdentityUserIdClosure: ((String) async throws -> UserIdentity?)? + + open override func getUserIdentity(userId: String) async throws -> UserIdentity? { + if let error = getUserIdentityUserIdThrowableError { + throw error + } + getUserIdentityUserIdCallsCount += 1 + getUserIdentityUserIdReceivedUserId = userId + DispatchQueue.main.async { + self.getUserIdentityUserIdReceivedInvocations.append(userId) + } + if let getUserIdentityUserIdClosure = getUserIdentityUserIdClosure { + return try await getUserIdentityUserIdClosure(userId) + } else { + return getUserIdentityUserIdReturnValue + } + } + //MARK: - isLastDevice open var isLastDeviceThrowableError: Error? @@ -20854,6 +20929,122 @@ open class UnreadNotificationsCountSDKMock: MatrixRustSDK.UnreadNotificationsCou } } } +open class UserIdentitySDKMock: MatrixRustSDK.UserIdentity { + init() { + super.init(noPointer: .init()) + } + + public required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + fatalError("init(unsafeFromRawPointer:) has not been implemented") + } + + fileprivate var pointer: UnsafeMutableRawPointer! + + //MARK: - masterKey + + var masterKeyUnderlyingCallsCount = 0 + open var masterKeyCallsCount: Int { + get { + if Thread.isMainThread { + return masterKeyUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = masterKeyUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + masterKeyUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + masterKeyUnderlyingCallsCount = newValue + } + } + } + } + open var masterKeyCalled: Bool { + return masterKeyCallsCount > 0 + } + + var masterKeyUnderlyingReturnValue: String? + open var masterKeyReturnValue: String? { + get { + if Thread.isMainThread { + return masterKeyUnderlyingReturnValue + } else { + var returnValue: String?? = nil + DispatchQueue.main.sync { + returnValue = masterKeyUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + masterKeyUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + masterKeyUnderlyingReturnValue = newValue + } + } + } + } + open var masterKeyClosure: (() -> String?)? + + open override func masterKey() -> String? { + masterKeyCallsCount += 1 + if let masterKeyClosure = masterKeyClosure { + return masterKeyClosure() + } else { + return masterKeyReturnValue + } + } + + //MARK: - pin + + open var pinThrowableError: Error? + var pinUnderlyingCallsCount = 0 + open var pinCallsCount: Int { + get { + if Thread.isMainThread { + return pinUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = pinUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + pinUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + pinUnderlyingCallsCount = newValue + } + } + } + } + open var pinCalled: Bool { + return pinCallsCount > 0 + } + open var pinClosure: (() async throws -> Void)? + + open override func pin() async throws { + if let error = pinThrowableError { + throw error + } + pinCallsCount += 1 + try await pinClosure?() + } +} open class WidgetDriverSDKMock: MatrixRustSDK.WidgetDriver { init() { super.init(noPointer: .init()) diff --git a/ElementX/Sources/Mocks/MediaProviderMock.swift b/ElementX/Sources/Mocks/MediaProviderMock.swift index da7a26909f..eb1929eff4 100644 --- a/ElementX/Sources/Mocks/MediaProviderMock.swift +++ b/ElementX/Sources/Mocks/MediaProviderMock.swift @@ -42,7 +42,7 @@ extension MediaProviderMock { return .success(data) } - loadFileFromSourceBodyReturnValue = .failure(.failedRetrievingFile) + loadFileFromSourceFilenameReturnValue = .failure(.failedRetrievingFile) loadImageRetryingOnReconnectionSizeClosure = { _, _ in Task { diff --git a/ElementX/Sources/Other/SwiftUI/Views/LoadableImage.swift b/ElementX/Sources/Other/SwiftUI/Views/LoadableImage.swift index 6bf098b997..1f909688bb 100644 --- a/ElementX/Sources/Other/SwiftUI/Views/LoadableImage.swift +++ b/ElementX/Sources/Other/SwiftUI/Views/LoadableImage.swift @@ -386,7 +386,7 @@ struct LoadableImage_Previews: PreviewProvider, TestablePreview { if isLoading { mediaProvider.imageFromSourceSizeClosure = { _, _ in nil } - mediaProvider.loadFileFromSourceBodyClosure = { _, _ in .failure(.failedRetrievingFile) } + mediaProvider.loadFileFromSourceFilenameClosure = { _, _ in .failure(.failedRetrievingFile) } mediaProvider.loadImageDataFromSourceClosure = { _ in .failure(.failedRetrievingImage) } mediaProvider.loadImageFromSourceSizeClosure = { _, _ in .failure(.failedRetrievingImage) } mediaProvider.loadThumbnailForSourceSourceSizeClosure = { _, _ in .failure(.failedRetrievingThumbnail) } diff --git a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/View/MessageComposer.swift b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/View/MessageComposer.swift index 85e0fd018d..61a3a6911c 100644 --- a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/View/MessageComposer.swift +++ b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/View/MessageComposer.swift @@ -206,16 +206,26 @@ struct MessageComposer_Previews: PreviewProvider, TestablePreview { static let replyTypes: [TimelineItemReplyDetails] = [ .loaded(sender: .init(id: "Dave"), eventID: "123", - eventContent: .message(.audio(.init(body: "Audio: Ride the lightning", duration: 100, waveform: nil, source: nil, contentType: nil)))), + eventContent: .message(.audio(.init(filename: "lightning.mp3", + caption: "Audio: Ride the lightning", + duration: 100, + waveform: nil, + source: nil, + contentType: nil)))), .loaded(sender: .init(id: "James"), eventID: "123", eventContent: .message(.emote(.init(body: "Emote: James thinks he's the phantom lord")))), .loaded(sender: .init(id: "Robert"), eventID: "123", - eventContent: .message(.file(.init(body: "File: Crash course in brain surgery.pdf", source: nil, thumbnailSource: nil, contentType: nil)))), + eventContent: .message(.file(.init(filename: "brain-surgery.pdf", + caption: "File: Crash course in brain surgery", + source: nil, + thumbnailSource: nil, + contentType: nil)))), .loaded(sender: .init(id: "Cliff"), eventID: "123", - eventContent: .message(.image(.init(body: "Image: Pushead", + eventContent: .message(.image(.init(filename: "head.png", + caption: "Image: Pushead", source: .init(url: .picturesDirectory, mimeType: nil), thumbnailSource: .init(url: .picturesDirectory, mimeType: nil))))), .loaded(sender: .init(id: "Jason"), @@ -226,7 +236,8 @@ struct MessageComposer_Previews: PreviewProvider, TestablePreview { eventContent: .message(.text(.init(body: "Text: Where the wild things are")))), .loaded(sender: .init(id: "Lars"), eventID: "123", - eventContent: .message(.video(.init(body: "Video: Through the never", + eventContent: .message(.video(.init(filename: "never.mov", + caption: "Video: Through the never", duration: 100, source: nil, thumbnailSource: .init(url: .picturesDirectory, mimeType: nil))))), diff --git a/ElementX/Sources/Screens/Timeline/TimelineInteractionHandler.swift b/ElementX/Sources/Screens/Timeline/TimelineInteractionHandler.swift index b4ad60281e..7696f2501a 100644 --- a/ElementX/Sources/Screens/Timeline/TimelineInteractionHandler.swift +++ b/ElementX/Sources/Screens/Timeline/TimelineInteractionHandler.swift @@ -532,30 +532,35 @@ class TimelineInteractionHandler { private func displayMediaActionIfPossible(timelineItem: RoomTimelineItemProtocol) async -> RoomTimelineControllerAction { var source: MediaSourceProxy? - var body: String + var filename: String + var caption: String? switch timelineItem { case let item as ImageRoomTimelineItem: source = item.content.source - body = item.content.body + filename = item.content.filename + caption = item.content.caption case let item as VideoRoomTimelineItem: source = item.content.source - body = item.content.body + filename = item.content.filename + caption = item.content.caption case let item as FileRoomTimelineItem: source = item.content.source - body = item.content.body + filename = item.content.filename + caption = item.content.caption case let item as AudioRoomTimelineItem: // For now we are just displaying audio messages with the File preview until we create a timeline player for them. source = item.content.source - body = item.content.body + filename = item.content.filename + caption = item.content.caption default: return .none } guard let source else { return .none } - switch await mediaProvider.loadFileFromSource(source, body: body) { + switch await mediaProvider.loadFileFromSource(source, filename: filename) { case .success(let file): - return .displayMediaFile(file: file, title: body) + return .displayMediaFile(file: file, title: caption ?? filename) case .failure: return .none } diff --git a/ElementX/Sources/Screens/Timeline/View/Replies/TimelineReplyView.swift b/ElementX/Sources/Screens/Timeline/View/Replies/TimelineReplyView.swift index 0a1b88bd8c..6326e84a32 100644 --- a/ElementX/Sources/Screens/Timeline/View/Replies/TimelineReplyView.swift +++ b/ElementX/Sources/Screens/Timeline/View/Replies/TimelineReplyView.swift @@ -26,8 +26,8 @@ struct TimelineReplyView: View { switch content { case .audio(let content): ReplyView(sender: sender, - plainBody: content.body, - formattedBody: nil, + plainBody: content.caption ?? content.filename, + formattedBody: content.formattedCaption, icon: .init(kind: .systemIcon("waveform"), cornerRadii: iconCornerRadii)) case .emote(let content): ReplyView(sender: sender, @@ -35,13 +35,13 @@ struct TimelineReplyView: View { formattedBody: content.formattedBody) case .file(let content): ReplyView(sender: sender, - plainBody: content.body, - formattedBody: nil, + plainBody: content.caption ?? content.filename, + formattedBody: content.formattedCaption, icon: .init(kind: .icon(\.document), cornerRadii: iconCornerRadii)) case .image(let content): ReplyView(sender: sender, - plainBody: content.body, - formattedBody: nil, + plainBody: content.caption ?? content.filename, + formattedBody: content.formattedCaption, icon: .init(kind: .mediaSource(content.thumbnailSource ?? content.source), cornerRadii: iconCornerRadii)) case .notice(let content): ReplyView(sender: sender, @@ -53,8 +53,8 @@ struct TimelineReplyView: View { formattedBody: content.formattedBody) case .video(let content): ReplyView(sender: sender, - plainBody: content.body, - formattedBody: nil, + plainBody: content.caption ?? content.filename, + formattedBody: content.formattedCaption, icon: content.thumbnailSource.map { .init(kind: .mediaSource($0), cornerRadii: iconCornerRadii) }) case .voice: ReplyView(sender: sender, @@ -247,7 +247,8 @@ struct TimelineReplyView_Previews: PreviewProvider, TestablePreview { TimelineReplyView(placement: .timeline, timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"), eventID: "123", - eventContent: .message(.audio(.init(body: "Some audio", + eventContent: .message(.audio(.init(filename: "audio.m4a", + caption: "Some audio", duration: 0, waveform: nil, source: nil, @@ -256,7 +257,8 @@ struct TimelineReplyView_Previews: PreviewProvider, TestablePreview { TimelineReplyView(placement: .timeline, timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"), eventID: "123", - eventContent: .message(.file(.init(body: "Some file", + eventContent: .message(.file(.init(filename: "file.txt", + caption: "Some file", source: nil, thumbnailSource: nil, contentType: nil))))), @@ -264,14 +266,16 @@ struct TimelineReplyView_Previews: PreviewProvider, TestablePreview { TimelineReplyView(placement: .timeline, timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"), eventID: "123", - eventContent: .message(.image(.init(body: "Some image", + eventContent: .message(.image(.init(filename: "image.jpg", + caption: "Some image", source: imageSource, thumbnailSource: imageSource))))), TimelineReplyView(placement: .timeline, timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"), eventID: "123", - eventContent: .message(.video(.init(body: "Some video", + eventContent: .message(.video(.init(filename: "video.mp4", + caption: "Some video", duration: 0, source: nil, thumbnailSource: imageSource))))), @@ -283,7 +287,8 @@ struct TimelineReplyView_Previews: PreviewProvider, TestablePreview { TimelineReplyView(placement: .timeline, timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"), eventID: "123", - eventContent: .message(.voice(.init(body: "Some voice message", + eventContent: .message(.voice(.init(filename: "voice-message.ogg", + caption: "Some voice message", duration: 0, waveform: nil, source: nil, diff --git a/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbledStylerView.swift b/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbledStylerView.swift index 9b0213f25c..7d809e2cb2 100644 --- a/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbledStylerView.swift +++ b/ElementX/Sources/Screens/Timeline/View/Style/TimelineItemBubbledStylerView.swift @@ -355,7 +355,7 @@ private extension View { struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview { static let viewModel = TimelineViewModel.mock static let viewModelWithPins: TimelineViewModel = { - let roomProxy = JoinedRoomProxyMock(.init(name: "Preview Room", pinnedEventIDs: [""])) + let roomProxy = JoinedRoomProxyMock(.init(name: "Preview Room", pinnedEventIDs: ["pinned"])) return TimelineViewModel(roomProxy: roomProxy, focussedEventID: nil, timelineController: MockRoomTimelineController(), @@ -385,113 +385,6 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview .snapshotPreferences(delay: 2.0) } - // These always include a reply - static var threads: some View { - ScrollView { - RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(uniqueID: ""), - timestamp: "10:42", - isOutgoing: true, - isEditable: false, - canBeRepliedTo: true, - isThreaded: true, - sender: .init(id: "whoever"), - content: .init(body: "A long message that should be on multiple lines."), - replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"), - eventID: "123", - eventContent: .message(.text(.init(body: "Short"))))), - groupStyle: .single)) - - AudioRoomTimelineView(timelineItem: .init(id: .init(uniqueID: ""), - timestamp: "10:42", - isOutgoing: true, - isEditable: false, - canBeRepliedTo: true, - isThreaded: true, - sender: .init(id: ""), - content: .init(body: "audio.ogg", - duration: 100, - waveform: EstimatedWaveform.mockWaveform, - source: nil, - contentType: nil), - replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"), - eventID: "123", - eventContent: .message(.text(.init(body: "Short")))))) - - FileRoomTimelineView(timelineItem: .init(id: .init(uniqueID: ""), - timestamp: "10:42", - isOutgoing: false, - isEditable: false, - canBeRepliedTo: true, - isThreaded: true, - sender: .init(id: ""), - content: .init(body: "File", - source: nil, - thumbnailSource: nil, - contentType: nil), - replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"), - eventID: "123", - eventContent: .message(.text(.init(body: "Short")))))) - ImageRoomTimelineView(timelineItem: .init(id: .init(uniqueID: ""), - timestamp: "10:42", - isOutgoing: true, - isEditable: true, - canBeRepliedTo: true, - isThreaded: true, - sender: .init(id: ""), - content: .init(body: "Some image", source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/png"), thumbnailSource: nil), - replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"), - eventID: "123", - eventContent: .message(.text(.init(body: "Short")))))) - LocationRoomTimelineView(timelineItem: .init(id: .random, - timestamp: "Now", - isOutgoing: false, - isEditable: false, - canBeRepliedTo: true, - isThreaded: true, - sender: .init(id: "Bob"), - content: .init(body: "Fallback geo uri description", - geoURI: .init(latitude: 41.902782, - longitude: 12.496366), - description: "Location description description description description description description description description"), - replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"), - eventID: "123", - eventContent: .message(.text(.init(body: "Short")))))) - LocationRoomTimelineView(timelineItem: .init(id: .random, - timestamp: "Now", - isOutgoing: false, - isEditable: false, - canBeRepliedTo: true, - isThreaded: true, - sender: .init(id: "Bob"), - content: .init(body: "Fallback geo uri description", - geoURI: .init(latitude: 41.902782, longitude: 12.496366), description: nil), - replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"), - eventID: "123", - eventContent: .message(.text(.init(body: "Short")))))) - - VoiceMessageRoomTimelineView(timelineItem: .init(id: .init(uniqueID: ""), - timestamp: "10:42", - isOutgoing: true, - isEditable: false, - canBeRepliedTo: true, - isThreaded: true, - sender: .init(id: ""), - content: .init(body: "audio.ogg", - duration: 100, - waveform: EstimatedWaveform.mockWaveform, - source: nil, - contentType: nil), - replyDetails: .loaded(sender: .init(id: "", displayName: "Alice"), - eventID: "123", - eventContent: .message(.text(.init(body: "Short"))))), - playerState: AudioPlayerState(id: .timelineItemIdentifier(.random), - title: L10n.commonVoiceMessage, - duration: 10, - waveform: EstimatedWaveform.mockWaveform)) - } - .environmentObject(viewModel.context) - } - static var mockTimeline: some View { ScrollView { VStack(alignment: .leading, spacing: 0) { @@ -502,7 +395,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview } .environmentObject(viewModel.context) } - + static var replies: some View { VStack(spacing: 0) { RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(uniqueID: ""), @@ -533,7 +426,21 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview } .environmentObject(viewModel.context) } - + + static var threads: some View { + ScrollView { + MockTimelineContent(isThreaded: true) + } + .environmentObject(viewModel.context) + } + + static var pinned: some View { + ScrollView { + MockTimelineContent(isPinned: true) + } + .environmentObject(viewModelWithPins.context) + } + static var encryptionAuthenticity: some View { VStack(spacing: 0) { RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(uniqueID: ""), @@ -588,7 +495,9 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview canBeRepliedTo: true, isThreaded: false, sender: .init(id: "Bob"), - content: .init(body: "Some other image", source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/png"), thumbnailSource: nil), + content: .init(filename: "other.png", + source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/png"), + thumbnailSource: nil), properties: RoomTimelineItemProperties(encryptionAuthenticity: .notGuaranteed(color: .gray)))) @@ -599,7 +508,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview canBeRepliedTo: true, isThreaded: true, sender: .init(id: ""), - content: .init(body: "audio.ogg", + content: .init(filename: "audio.ogg", duration: 100, waveform: EstimatedWaveform.mockWaveform, source: nil, @@ -612,96 +521,114 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview } .environmentObject(viewModel.context) } - - static var pinned: some View { - ScrollView { - RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(uniqueID: "", eventOrTransactionID: .eventId(eventId: "")), - timestamp: "10:42", - isOutgoing: true, - isEditable: false, - canBeRepliedTo: true, - isThreaded: false, - sender: .init(id: "whoever"), - content: .init(body: "A long message that should be on multiple lines."), - replyDetails: nil), - groupStyle: .single)) +} - AudioRoomTimelineView(timelineItem: .init(id: .init(uniqueID: "", eventOrTransactionID: .eventId(eventId: "")), - timestamp: "10:42", - isOutgoing: true, - isEditable: false, - canBeRepliedTo: true, - isThreaded: false, - sender: .init(id: ""), - content: .init(body: "audio.ogg", - duration: 100, - waveform: EstimatedWaveform.mockWaveform, - source: nil, - contentType: nil), - replyDetails: nil)) - - FileRoomTimelineView(timelineItem: .init(id: .init(uniqueID: "", eventOrTransactionID: .eventId(eventId: "")), - timestamp: "10:42", +private struct MockTimelineContent: View { + var isThreaded = false + var isPinned = false + + var body: some View { + RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: makeItemIdentifier(), + timestamp: "10:42", + isOutgoing: true, + isEditable: false, + canBeRepliedTo: true, + isThreaded: isThreaded, + sender: .init(id: "whoever"), + content: .init(body: "A long message that should be on multiple lines."), + replyDetails: replyDetails), + groupStyle: .single)) + + AudioRoomTimelineView(timelineItem: .init(id: makeItemIdentifier(), + timestamp: "10:42", + isOutgoing: true, + isEditable: false, + canBeRepliedTo: true, + isThreaded: isThreaded, + sender: .init(id: ""), + content: .init(filename: "audio.ogg", + duration: 100, + waveform: EstimatedWaveform.mockWaveform, + source: nil, + contentType: nil), + replyDetails: replyDetails)) + + FileRoomTimelineView(timelineItem: .init(id: makeItemIdentifier(), + timestamp: "10:42", + isOutgoing: false, + isEditable: false, + canBeRepliedTo: true, + isThreaded: isThreaded, + sender: .init(id: ""), + content: .init(filename: "file.txt", + caption: "File", + source: nil, + thumbnailSource: nil, + contentType: nil), + replyDetails: replyDetails)) + + ImageRoomTimelineView(timelineItem: .init(id: makeItemIdentifier(), + timestamp: "10:42", + isOutgoing: true, + isEditable: true, + canBeRepliedTo: true, + isThreaded: isThreaded, + sender: .init(id: ""), + content: .init(filename: "image.jpg", + source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/png"), + thumbnailSource: nil), + replyDetails: replyDetails)) + + LocationRoomTimelineView(timelineItem: .init(id: makeItemIdentifier(), + timestamp: "Now", isOutgoing: false, isEditable: false, canBeRepliedTo: true, - isThreaded: false, - sender: .init(id: ""), - content: .init(body: "File", - source: nil, - thumbnailSource: nil, - contentType: nil), - replyDetails: nil)) - ImageRoomTimelineView(timelineItem: .init(id: .init(uniqueID: "", eventOrTransactionID: .eventId(eventId: "")), - timestamp: "10:42", - isOutgoing: true, - isEditable: true, - canBeRepliedTo: true, - isThreaded: false, - sender: .init(id: ""), - content: .init(body: "Some image", source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/png"), thumbnailSource: nil), - replyDetails: nil)) - LocationRoomTimelineView(timelineItem: .init(id: .init(uniqueID: "", eventOrTransactionID: .eventId(eventId: "")), - timestamp: "Now", - isOutgoing: false, - isEditable: false, - canBeRepliedTo: true, - isThreaded: false, - sender: .init(id: "Bob"), - content: .init(body: "Fallback geo uri description", - geoURI: .init(latitude: 41.902782, - longitude: 12.496366), - description: "Location description description description description description description description description"), - replyDetails: nil)) - LocationRoomTimelineView(timelineItem: .init(id: .init(uniqueID: "", eventOrTransactionID: .eventId(eventId: "")), - timestamp: "Now", - isOutgoing: false, + isThreaded: isThreaded, + sender: .init(id: "Bob"), + content: .init(body: "Fallback geo uri description", + geoURI: .init(latitude: 41.902782, + longitude: 12.496366), + description: "Location description description description description description description description description"), + replyDetails: replyDetails)) + + LocationRoomTimelineView(timelineItem: .init(id: makeItemIdentifier(), + timestamp: "Now", + isOutgoing: false, + isEditable: false, + canBeRepliedTo: true, + isThreaded: isThreaded, + sender: .init(id: "Bob"), + content: .init(body: "Fallback geo uri description", + geoURI: .init(latitude: 41.902782, longitude: 12.496366), description: nil), + replyDetails: replyDetails)) + + VoiceMessageRoomTimelineView(timelineItem: .init(id: makeItemIdentifier(), + timestamp: "10:42", + isOutgoing: true, isEditable: false, canBeRepliedTo: true, - isThreaded: false, - sender: .init(id: "Bob"), - content: .init(body: "Fallback geo uri description", - geoURI: .init(latitude: 41.902782, longitude: 12.496366), description: nil), - replyDetails: nil)) - - VoiceMessageRoomTimelineView(timelineItem: .init(id: .init(uniqueID: "", eventOrTransactionID: .eventId(eventId: "")), - timestamp: "10:42", - isOutgoing: true, - isEditable: false, - canBeRepliedTo: true, - isThreaded: false, - sender: .init(id: ""), - content: .init(body: "audio.ogg", - duration: 100, - waveform: EstimatedWaveform.mockWaveform, - source: nil, - contentType: nil), - replyDetails: nil), - playerState: AudioPlayerState(id: .timelineItemIdentifier(.random), - title: L10n.commonVoiceMessage, - duration: 10, - waveform: EstimatedWaveform.mockWaveform)) - } - .environmentObject(viewModelWithPins.context) + isThreaded: isThreaded, + sender: .init(id: ""), + content: .init(filename: "audio.ogg", + duration: 100, + waveform: EstimatedWaveform.mockWaveform, + source: nil, + contentType: nil), + replyDetails: replyDetails), + playerState: AudioPlayerState(id: .timelineItemIdentifier(.random), + title: L10n.commonVoiceMessage, + duration: 10, + waveform: EstimatedWaveform.mockWaveform)) + } + + func makeItemIdentifier() -> TimelineItemIdentifier { + isPinned ? .init(uniqueID: "", eventOrTransactionID: .eventId(eventId: "pinned")) : .random + } + + var replyDetails: TimelineItemReplyDetails? { + isThreaded ? .loaded(sender: .init(id: "", displayName: "Alice"), + eventID: "123", + eventContent: .message(.text(.init(body: "Short")))) : nil } } diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/AudioRoomTimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/AudioRoomTimelineView.swift index a116d81efe..59952d1185 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/AudioRoomTimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/AudioRoomTimelineView.swift @@ -41,6 +41,10 @@ struct AudioRoomTimelineView_Previews: PreviewProvider, TestablePreview { canBeRepliedTo: true, isThreaded: false, sender: .init(id: "Bob"), - content: .init(body: "audio.ogg", duration: 300, waveform: nil, source: nil, contentType: nil))) + content: .init(filename: "audio.ogg", + duration: 300, + waveform: nil, + source: nil, + contentType: nil))) } } diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/FileRoomTimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/FileRoomTimelineView.swift index faf03fcf13..a75a139b38 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/FileRoomTimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/FileRoomTimelineView.swift @@ -42,7 +42,10 @@ struct FileRoomTimelineView_Previews: PreviewProvider, TestablePreview { canBeRepliedTo: true, isThreaded: false, sender: .init(id: "Bob"), - content: .init(body: "document.pdf", source: nil, thumbnailSource: nil, contentType: nil))) + content: .init(filename: "document.pdf", + source: nil, + thumbnailSource: nil, + contentType: nil))) FileRoomTimelineView(timelineItem: FileRoomTimelineItem(id: .random, timestamp: "Now", @@ -51,7 +54,10 @@ struct FileRoomTimelineView_Previews: PreviewProvider, TestablePreview { canBeRepliedTo: true, isThreaded: false, sender: .init(id: "Bob"), - content: .init(body: "document.docx", source: nil, thumbnailSource: nil, contentType: nil))) + content: .init(filename: "document.docx", + source: nil, + thumbnailSource: nil, + contentType: nil))) FileRoomTimelineView(timelineItem: FileRoomTimelineItem(id: .random, timestamp: "Now", @@ -60,7 +66,10 @@ struct FileRoomTimelineView_Previews: PreviewProvider, TestablePreview { canBeRepliedTo: true, isThreaded: false, sender: .init(id: "Bob"), - content: .init(body: "document.txt", source: nil, thumbnailSource: nil, contentType: nil))) + content: .init(filename: "document.txt", + source: nil, + 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 c213ead505..9c8c2471a6 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/ImageRoomTimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/ImageRoomTimelineView.swift @@ -59,7 +59,9 @@ struct ImageRoomTimelineView_Previews: PreviewProvider, TestablePreview { canBeRepliedTo: true, isThreaded: false, sender: .init(id: "Bob"), - content: .init(body: "Some image", source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/png"), thumbnailSource: nil))) + content: .init(filename: "image.jpg", + source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/jpg"), + thumbnailSource: nil))) ImageRoomTimelineView(timelineItem: ImageRoomTimelineItem(id: .random, timestamp: "Now", @@ -68,7 +70,9 @@ struct ImageRoomTimelineView_Previews: PreviewProvider, TestablePreview { canBeRepliedTo: true, isThreaded: false, sender: .init(id: "Bob"), - content: .init(body: "Some other image", source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/png"), thumbnailSource: nil))) + content: .init(filename: "other.png", + source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/png"), + thumbnailSource: nil))) ImageRoomTimelineView(timelineItem: ImageRoomTimelineItem(id: .random, timestamp: "Now", @@ -77,7 +81,7 @@ struct ImageRoomTimelineView_Previews: PreviewProvider, TestablePreview { canBeRepliedTo: true, isThreaded: false, sender: .init(id: "Bob"), - content: .init(body: "Blurhashed image", + content: .init(filename: "Blurhashed.jpg", source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/gif"), thumbnailSource: nil, aspectRatio: 0.7, diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/VideoRoomTimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/VideoRoomTimelineView.swift index 23bc73d589..fd530ff938 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/VideoRoomTimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/VideoRoomTimelineView.swift @@ -70,7 +70,10 @@ struct VideoRoomTimelineView_Previews: PreviewProvider, TestablePreview { canBeRepliedTo: true, isThreaded: false, sender: .init(id: "Bob"), - content: .init(body: "Some video", duration: 21, source: nil, thumbnailSource: nil))) + content: .init(filename: "video.mp4", + duration: 21, + source: nil, + thumbnailSource: nil))) VideoRoomTimelineView(timelineItem: VideoRoomTimelineItem(id: .random, timestamp: "Now", @@ -79,7 +82,10 @@ struct VideoRoomTimelineView_Previews: PreviewProvider, TestablePreview { canBeRepliedTo: true, isThreaded: false, sender: .init(id: "Bob"), - content: .init(body: "Some other video", duration: 22, source: nil, thumbnailSource: nil))) + content: .init(filename: "other.mp4", + duration: 22, + source: nil, + thumbnailSource: nil))) VideoRoomTimelineView(timelineItem: VideoRoomTimelineItem(id: .random, timestamp: "Now", @@ -88,7 +94,12 @@ struct VideoRoomTimelineView_Previews: PreviewProvider, TestablePreview { canBeRepliedTo: true, isThreaded: false, sender: .init(id: "Bob"), - content: .init(body: "Blurhashed video", duration: 23, source: nil, thumbnailSource: nil, aspectRatio: 0.7, blurhash: "L%KUc%kqS$RP?Ks,WEf8OlrqaekW"))) + content: .init(filename: "Blurhashed.mp4", + duration: 23, + source: nil, + thumbnailSource: nil, + aspectRatio: 0.7, + blurhash: "L%KUc%kqS$RP?Ks,WEf8OlrqaekW"))) } } } diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index 3ab6cfaac8..8ed91d96c4 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -929,8 +929,8 @@ extension ClientProxy: MediaLoaderProtocol { try await mediaLoader.loadMediaThumbnailForSource(source, width: width, height: height) } - func loadMediaFileForSource(_ source: MediaSourceProxy, body: String?) async throws -> MediaFileHandleProxy { - try await mediaLoader.loadMediaFileForSource(source, body: body) + func loadMediaFileForSource(_ source: MediaSourceProxy, filename: String?) async throws -> MediaFileHandleProxy { + try await mediaLoader.loadMediaFileForSource(source, filename: filename) } } diff --git a/ElementX/Sources/Services/Media/Provider/MediaLoader.swift b/ElementX/Sources/Services/Media/Provider/MediaLoader.swift index 427aeb77c5..fe4e3b2fcc 100644 --- a/ElementX/Sources/Services/Media/Provider/MediaLoader.swift +++ b/ElementX/Sources/Services/Media/Provider/MediaLoader.swift @@ -34,8 +34,8 @@ actor MediaLoader: MediaLoaderProtocol { } } - func loadMediaFileForSource(_ source: MediaSourceProxy, body: String?) async throws -> MediaFileHandleProxy { - let result = try await client.getMediaFile(mediaSource: source.underlyingSource, body: body, mimeType: source.mimeType ?? "application/octet-stream", useCache: true, tempDir: nil) + func loadMediaFileForSource(_ source: MediaSourceProxy, filename: String?) async throws -> MediaFileHandleProxy { + let result = try await client.getMediaFile(mediaSource: source.underlyingSource, body: filename, mimeType: source.mimeType ?? "application/octet-stream", useCache: true, tempDir: nil) return MediaFileHandleProxy(handle: result) } diff --git a/ElementX/Sources/Services/Media/Provider/MediaLoaderProtocol.swift b/ElementX/Sources/Services/Media/Provider/MediaLoaderProtocol.swift index a19ef95a81..7600f47358 100644 --- a/ElementX/Sources/Services/Media/Provider/MediaLoaderProtocol.swift +++ b/ElementX/Sources/Services/Media/Provider/MediaLoaderProtocol.swift @@ -13,5 +13,5 @@ protocol MediaLoaderProtocol { func loadMediaThumbnailForSource(_ source: MediaSourceProxy, width: UInt, height: UInt) async throws -> Data - func loadMediaFileForSource(_ source: MediaSourceProxy, body: String?) async throws -> MediaFileHandleProxy + func loadMediaFileForSource(_ source: MediaSourceProxy, filename: String?) async throws -> MediaFileHandleProxy } diff --git a/ElementX/Sources/Services/Media/Provider/MediaProvider.swift b/ElementX/Sources/Services/Media/Provider/MediaProvider.swift index 408e858748..a3c3ab7d28 100644 --- a/ElementX/Sources/Services/Media/Provider/MediaProvider.swift +++ b/ElementX/Sources/Services/Media/Provider/MediaProvider.swift @@ -117,9 +117,9 @@ struct MediaProvider: MediaProviderProtocol { // MARK: Files - func loadFileFromSource(_ source: MediaSourceProxy, body: String?) async -> Result { + func loadFileFromSource(_ source: MediaSourceProxy, filename: String?) async -> Result { do { - let file = try await mediaLoader.loadMediaFileForSource(source, body: body) + let file = try await mediaLoader.loadMediaFileForSource(source, filename: filename) return .success(file) } catch { MXLog.error("Failed retrieving file with error: \(error)") diff --git a/ElementX/Sources/Services/Media/Provider/MediaProviderProtocol.swift b/ElementX/Sources/Services/Media/Provider/MediaProviderProtocol.swift index bae831b446..6a59172eca 100644 --- a/ElementX/Sources/Services/Media/Provider/MediaProviderProtocol.swift +++ b/ElementX/Sources/Services/Media/Provider/MediaProviderProtocol.swift @@ -25,7 +25,7 @@ protocol MediaProviderProtocol { func loadThumbnailForSource(source: MediaSourceProxy, size: CGSize) async -> Result - func loadFileFromSource(_ source: MediaSourceProxy, body: String?) async -> Result + func loadFileFromSource(_ source: MediaSourceProxy, filename: String?) async -> Result } extension MediaProviderProtocol { @@ -38,6 +38,6 @@ extension MediaProviderProtocol { } func loadFileFromSource(_ source: MediaSourceProxy) async -> Result { - await loadFileFromSource(source, body: nil) + await loadFileFromSource(source, filename: nil) } } diff --git a/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift b/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift index 820e880b81..ff84de4355 100644 --- a/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift +++ b/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift @@ -257,7 +257,7 @@ enum RoomTimelineItemFixtures { canBeRepliedTo: true, isThreaded: false, sender: .init(id: ""), - content: .init(body: "video", + content: .init(filename: "video.mp4", duration: 100, source: .init(url: .picturesDirectory, mimeType: nil), thumbnailSource: .init(url: .picturesDirectory, mimeType: nil), @@ -272,7 +272,7 @@ enum RoomTimelineItemFixtures { canBeRepliedTo: true, isThreaded: false, sender: .init(id: ""), - content: .init(body: "image", + content: .init(filename: "image.jpg", source: .init(url: .picturesDirectory, mimeType: nil), thumbnailSource: nil, width: 5120, diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/AudioRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/AudioRoomTimelineItem.swift index 6d99259b46..6b556dcbe0 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/AudioRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/AudioRoomTimelineItem.swift @@ -23,7 +23,7 @@ struct AudioRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable { var properties = RoomTimelineItemProperties() var body: String { - content.body + content.caption ?? content.filename } var contentType: EventBasedMessageTimelineItemContentType { diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/AudioRoomTimelineItemContent.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/AudioRoomTimelineItemContent.swift index 6353b619e1..4ca8d5c1f1 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/AudioRoomTimelineItemContent.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/AudioRoomTimelineItemContent.swift @@ -9,7 +9,11 @@ import UIKit import UniformTypeIdentifiers struct AudioRoomTimelineItemContent: Hashable { - let body: String + let filename: String + var caption: String? + var formattedCaption: AttributedString? + /// The original textual representation of the formatted caption directly from the event (usually HTML code) + var formattedCaptionHTMLString: String? let duration: TimeInterval let waveform: EstimatedWaveform? let source: MediaSourceProxy? diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/FileRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/FileRoomTimelineItem.swift index 3b531a7e18..bb025ee776 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/FileRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/FileRoomTimelineItem.swift @@ -26,7 +26,7 @@ struct FileRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable { var properties = RoomTimelineItemProperties() var body: String { - content.body + content.caption ?? content.filename } var contentType: EventBasedMessageTimelineItemContentType { diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/FileRoomTimelineItemContent.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/FileRoomTimelineItemContent.swift index 9c0cfe9798..67f00ee1ba 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/FileRoomTimelineItemContent.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/FileRoomTimelineItemContent.swift @@ -9,7 +9,11 @@ import Foundation import UniformTypeIdentifiers struct FileRoomTimelineItemContent: Hashable { - let body: String + let filename: String + var caption: String? + var formattedCaption: AttributedString? + /// The original textual representation of the formatted caption directly from the event (usually HTML code) + var formattedCaptionHTMLString: String? let source: MediaSourceProxy? let thumbnailSource: MediaSourceProxy? let contentType: UTType? diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/ImageRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/ImageRoomTimelineItem.swift index 759e9d4567..b556b1e187 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/ImageRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/ImageRoomTimelineItem.swift @@ -25,7 +25,7 @@ struct ImageRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable { var properties = RoomTimelineItemProperties() var body: String { - content.body + content.caption ?? content.filename } var contentType: EventBasedMessageTimelineItemContentType { diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/ImageRoomTimelineItemContent.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/ImageRoomTimelineItemContent.swift index 0ef2e27aa2..01eb841510 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/ImageRoomTimelineItemContent.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/ImageRoomTimelineItemContent.swift @@ -9,7 +9,11 @@ import Foundation import UniformTypeIdentifiers struct ImageRoomTimelineItemContent: Hashable { - let body: String + let filename: String + var caption: String? + var formattedCaption: AttributedString? + /// The original textual representation of the formatted caption directly from the event (usually HTML code) + var formattedCaptionHTMLString: String? let source: MediaSourceProxy let thumbnailSource: MediaSourceProxy? var width: CGFloat? diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/VideoRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/VideoRoomTimelineItem.swift index 896d6a81ff..5247fd6cd2 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/VideoRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/VideoRoomTimelineItem.swift @@ -25,7 +25,7 @@ struct VideoRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable { var properties = RoomTimelineItemProperties() var body: String { - content.body + content.caption ?? content.filename } var contentType: EventBasedMessageTimelineItemContentType { diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/VideoRoomTimelineItemContent.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/VideoRoomTimelineItemContent.swift index 905008a6f3..0790970edf 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/VideoRoomTimelineItemContent.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/VideoRoomTimelineItemContent.swift @@ -9,7 +9,11 @@ import Foundation import UniformTypeIdentifiers struct VideoRoomTimelineItemContent: Hashable { - let body: String + let filename: String + var caption: String? + var formattedCaption: AttributedString? + /// The original textual representation of the formatted caption directly from the event (usually HTML code) + var formattedCaptionHTMLString: String? let duration: TimeInterval let source: MediaSourceProxy? let thumbnailSource: MediaSourceProxy? diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/VoiceMessageRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/VoiceMessageRoomTimelineItem.swift index 22837d2f5e..d8e0769988 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/VoiceMessageRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/VoiceMessageRoomTimelineItem.swift @@ -23,7 +23,7 @@ struct VoiceMessageRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equa var properties = RoomTimelineItemProperties() var body: String { - content.body + content.caption ?? content.filename } var contentType: EventBasedMessageTimelineItemContentType { diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/VoiceMessages/VoiceMessageRoomTimelineView.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/VoiceMessages/VoiceMessageRoomTimelineView.swift index 1cde05ca25..06ca0a137c 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/VoiceMessages/VoiceMessageRoomTimelineView.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/VoiceMessages/VoiceMessageRoomTimelineView.swift @@ -63,7 +63,7 @@ struct VoiceMessageRoomTimelineView_Previews: PreviewProvider, TestablePreview { canBeRepliedTo: true, isThreaded: false, sender: .init(id: "Bob"), - content: .init(body: "audio.ogg", + content: .init(filename: "audio.ogg", duration: 300, waveform: EstimatedWaveform.mockWaveform, source: nil, diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift index 75c1cfdd3f..f43d5e4c83 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift @@ -490,12 +490,18 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { } private func buildAudioTimelineItemContent(_ messageContent: AudioMessageContent) -> AudioRoomTimelineItemContent { + let htmlCaption = messageContent.formattedCaption?.format == .html ? messageContent.formattedCaption?.body : nil + let formattedCaption = htmlCaption != nil ? attributedStringBuilder.fromHTML(htmlCaption) : attributedStringBuilder.fromPlain(messageContent.caption) + var waveform: EstimatedWaveform? if let audioWaveform = messageContent.audio?.waveform { waveform = EstimatedWaveform(data: audioWaveform) } - return AudioRoomTimelineItemContent(body: messageContent.body, + return AudioRoomTimelineItemContent(filename: messageContent.filename, + caption: messageContent.caption, + formattedCaption: formattedCaption, + formattedCaptionHTMLString: htmlCaption, duration: messageContent.audio?.duration ?? 0, waveform: waveform, source: MediaSourceProxy(source: messageContent.source, mimeType: messageContent.info?.mimetype), @@ -503,6 +509,9 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { } private func buildImageTimelineItemContent(_ messageContent: ImageMessageContent) -> ImageRoomTimelineItemContent { + let htmlCaption = messageContent.formattedCaption?.format == .html ? messageContent.formattedCaption?.body : nil + let formattedCaption = htmlCaption != nil ? attributedStringBuilder.fromHTML(htmlCaption) : attributedStringBuilder.fromPlain(messageContent.caption) + let thumbnailSource = messageContent.info?.thumbnailSource.map { MediaSourceProxy(source: $0, mimeType: messageContent.info?.thumbnailInfo?.mimetype) } let width = messageContent.info?.width.map(CGFloat.init) let height = messageContent.info?.height.map(CGFloat.init) @@ -512,7 +521,10 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { aspectRatio = width / height } - return .init(body: messageContent.body, + return .init(filename: messageContent.filename, + caption: messageContent.caption, + formattedCaption: formattedCaption, + formattedCaptionHTMLString: htmlCaption, source: MediaSourceProxy(source: messageContent.source, mimeType: messageContent.info?.mimetype), thumbnailSource: thumbnailSource, width: width, @@ -523,6 +535,9 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { } private func buildVideoTimelineItemContent(_ messageContent: VideoMessageContent) -> VideoRoomTimelineItemContent { + let htmlCaption = messageContent.formattedCaption?.format == .html ? messageContent.formattedCaption?.body : nil + let formattedCaption = htmlCaption != nil ? attributedStringBuilder.fromHTML(htmlCaption) : attributedStringBuilder.fromPlain(messageContent.caption) + let thumbnailSource = messageContent.info?.thumbnailSource.map { MediaSourceProxy(source: $0, mimeType: messageContent.info?.thumbnailInfo?.mimetype) } let width = messageContent.info?.width.map(CGFloat.init) let height = messageContent.info?.height.map(CGFloat.init) @@ -532,7 +547,10 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { aspectRatio = width / height } - return .init(body: messageContent.body, + return .init(filename: messageContent.filename, + caption: messageContent.caption, + formattedCaption: formattedCaption, + formattedCaptionHTMLString: htmlCaption, duration: messageContent.info?.duration ?? 0, source: MediaSourceProxy(source: messageContent.source, mimeType: messageContent.info?.mimetype), thumbnailSource: thumbnailSource, @@ -550,9 +568,15 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { } private func buildFileTimelineItemContent(_ messageContent: FileMessageContent) -> FileRoomTimelineItemContent { + let htmlCaption = messageContent.formattedCaption?.format == .html ? messageContent.formattedCaption?.body : nil + let formattedCaption = htmlCaption != nil ? attributedStringBuilder.fromHTML(htmlCaption) : attributedStringBuilder.fromPlain(messageContent.caption) + let thumbnailSource = messageContent.info?.thumbnailSource.map { MediaSourceProxy(source: $0, mimeType: messageContent.info?.thumbnailInfo?.mimetype) } - return .init(body: messageContent.body, + return .init(filename: messageContent.filename, + caption: messageContent.caption, + formattedCaption: formattedCaption, + formattedCaptionHTMLString: htmlCaption, source: MediaSourceProxy(source: messageContent.source, mimeType: messageContent.info?.mimetype), thumbnailSource: thumbnailSource, contentType: UTType(mimeType: messageContent.info?.mimetype, fallbackFilename: messageContent.body)) diff --git a/ElementX/Sources/Services/VoiceMessage/VoiceMessageMediaManager.swift b/ElementX/Sources/Services/VoiceMessage/VoiceMessageMediaManager.swift index e3ae2931cc..b1606ef486 100644 --- a/ElementX/Sources/Services/VoiceMessage/VoiceMessageMediaManager.swift +++ b/ElementX/Sources/Services/VoiceMessage/VoiceMessageMediaManager.swift @@ -51,7 +51,7 @@ class VoiceMessageMediaManager: VoiceMessageMediaManagerProtocol { } // Otherwise, load the file from source - guard case .success(let fileHandle) = await mediaProvider.loadFileFromSource(source, body: body) else { + guard case .success(let fileHandle) = await mediaProvider.loadFileFromSource(source, filename: body) else { throw MediaProviderError.failedRetrievingFile } diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPad-en-GB.Replying-in-thread.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPad-en-GB.Replying-in-thread.png index 89a898ec33..2773e12577 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPad-en-GB.Replying-in-thread.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPad-en-GB.Replying-in-thread.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9b0e4caaf122d984b137f3d7bfebf701aa63def993cf2681ed06ddcdf4805af4 -size 197604 +oid sha256:6a034b807916e062e25c5fb59ecd09a47d0e1c7c822534b35e992fd9c839ab07 +size 196481 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPad-en-GB.Replying.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPad-en-GB.Replying.png index 89a898ec33..2773e12577 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPad-en-GB.Replying.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPad-en-GB.Replying.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9b0e4caaf122d984b137f3d7bfebf701aa63def993cf2681ed06ddcdf4805af4 -size 197604 +oid sha256:6a034b807916e062e25c5fb59ecd09a47d0e1c7c822534b35e992fd9c839ab07 +size 196481 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPad-pseudo.Replying-in-thread.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPad-pseudo.Replying-in-thread.png index d7d11181b2..26e53c7a36 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPad-pseudo.Replying-in-thread.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPad-pseudo.Replying-in-thread.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f54b2d9bafaf2e9f2fd304dfa8e1956d079a7280e9075d9ef1f1b3005314ae3a -size 200745 +oid sha256:ffe1ceda422b0bccc1e587036ea047bebb4ee4643f54bdf25ff2b3b530b6f7e2 +size 199662 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPad-pseudo.Replying.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPad-pseudo.Replying.png index d7d11181b2..26e53c7a36 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPad-pseudo.Replying.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPad-pseudo.Replying.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f54b2d9bafaf2e9f2fd304dfa8e1956d079a7280e9075d9ef1f1b3005314ae3a -size 200745 +oid sha256:ffe1ceda422b0bccc1e587036ea047bebb4ee4643f54bdf25ff2b3b530b6f7e2 +size 199662 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPhone-16-en-GB.Replying-in-thread.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPhone-16-en-GB.Replying-in-thread.png index 3e6c0bba4f..e58f1c116b 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPhone-16-en-GB.Replying-in-thread.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPhone-16-en-GB.Replying-in-thread.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d81827ae7b6798e844365ed7f0971d56df662cded25c03a300cf8a817afdbf51 -size 136742 +oid sha256:91bef32e5248f8084995b237b671cea005ce0eb9253a325165dd066a43934bfa +size 135848 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPhone-16-en-GB.Replying.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPhone-16-en-GB.Replying.png index 3e6c0bba4f..e58f1c116b 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPhone-16-en-GB.Replying.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPhone-16-en-GB.Replying.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d81827ae7b6798e844365ed7f0971d56df662cded25c03a300cf8a817afdbf51 -size 136742 +oid sha256:91bef32e5248f8084995b237b671cea005ce0eb9253a325165dd066a43934bfa +size 135848 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPhone-16-pseudo.Replying-in-thread.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPhone-16-pseudo.Replying-in-thread.png index b0b67401d5..39ebe44626 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPhone-16-pseudo.Replying-in-thread.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPhone-16-pseudo.Replying-in-thread.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f16f475a1f4fd5e3c2a00e294103721df1b8bd38f69ccac9f4b295efac0e8af5 -size 139891 +oid sha256:c5fd3ee0cc6bb3cf124cc66877d87cde800a3da7c10f8e26d6e19d884f4ca40e +size 139138 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPhone-16-pseudo.Replying.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPhone-16-pseudo.Replying.png index b0b67401d5..39ebe44626 100644 --- a/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPhone-16-pseudo.Replying.png +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/test_messageComposer-iPhone-16-pseudo.Replying.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f16f475a1f4fd5e3c2a00e294103721df1b8bd38f69ccac9f4b295efac0e8af5 -size 139891 +oid sha256:c5fd3ee0cc6bb3cf124cc66877d87cde800a3da7c10f8e26d6e19d884f4ca40e +size 139138 diff --git a/UnitTests/Sources/LoggingTests.swift b/UnitTests/Sources/LoggingTests.swift index 66e069fab1..9b22fd2660 100644 --- a/UnitTests/Sources/LoggingTests.swift +++ b/UnitTests/Sources/LoggingTests.swift @@ -149,7 +149,10 @@ class LoggingTests: XCTestCase { canBeRepliedTo: true, isThreaded: false, sender: .init(id: "sender"), - content: .init(body: "ImageString", source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/gif"), thumbnailSource: nil)) + content: .init(filename: "ImageString", + caption: "ImageString", + source: MediaSourceProxy(url: .picturesDirectory, mimeType: "image/gif"), + thumbnailSource: nil)) let videoMessage = VideoRoomTimelineItem(id: .random, timestamp: "", isOutgoing: false, @@ -157,7 +160,11 @@ class LoggingTests: XCTestCase { canBeRepliedTo: true, isThreaded: false, sender: .init(id: "sender"), - content: .init(body: "VideoString", duration: 0, source: nil, thumbnailSource: nil)) + content: .init(filename: "VideoString", + caption: "VideoString", + duration: 0, + source: nil, + thumbnailSource: nil)) let fileMessage = FileRoomTimelineItem(id: .random, timestamp: "", isOutgoing: false, @@ -165,7 +172,11 @@ class LoggingTests: XCTestCase { canBeRepliedTo: true, isThreaded: false, sender: .init(id: "sender"), - content: .init(body: "FileString", source: nil, thumbnailSource: nil, contentType: nil)) + content: .init(filename: "FileString", + caption: "FileString", + source: nil, + thumbnailSource: nil, + contentType: nil)) // When logging that value MXLog.configure(currentTarget: "tests", filePrefix: nil, logLevel: .info) diff --git a/UnitTests/Sources/VoiceMessageMediaManagerTests.swift b/UnitTests/Sources/VoiceMessageMediaManagerTests.swift index 8d8ed12812..7a85f15dc4 100644 --- a/UnitTests/Sources/VoiceMessageMediaManagerTests.swift +++ b/UnitTests/Sources/VoiceMessageMediaManagerTests.swift @@ -50,7 +50,7 @@ class VoiceMessageMediaManagerTests: XCTestCase { voiceMessageCache.fileURLForReturnValue = nil let mediaSource = MediaSourceProxy(url: someURL, mimeType: "audio/ogg; codecs=opus") - mediaProvider.loadFileFromSourceBodyReturnValue = .success(MediaFileHandleProxy.unmanaged(url: loadedFile)) + mediaProvider.loadFileFromSourceFilenameReturnValue = .success(MediaFileHandleProxy.unmanaged(url: loadedFile)) voiceMessageCache.cacheMediaSourceUsingMoveReturnValue = .success(cachedConvertedFileURL) voiceMessageMediaManager = VoiceMessageMediaManager(mediaProvider: mediaProvider, @@ -103,7 +103,7 @@ class VoiceMessageMediaManagerTests: XCTestCase { // Check if the file is not already present in cache voiceMessageCache.fileURLForReturnValue = nil let mediaSource = MediaSourceProxy(url: someURL, mimeType: audioOGGMimeType) - mediaProvider.loadFileFromSourceBodyReturnValue = .success(MediaFileHandleProxy.unmanaged(url: loadedFile)) + mediaProvider.loadFileFromSourceFilenameReturnValue = .success(MediaFileHandleProxy.unmanaged(url: loadedFile)) let audioConverter = AudioConverterMock() voiceMessageCache.cacheMediaSourceUsingMoveReturnValue = .success(cachedConvertedFileURL) voiceMessageMediaManager = VoiceMessageMediaManager(mediaProvider: mediaProvider, @@ -139,7 +139,7 @@ class VoiceMessageMediaManagerTests: XCTestCase { } let audioConverter = AudioConverterMock() - mediaProvider.loadFileFromSourceBodyReturnValue = .success(MediaFileHandleProxy.unmanaged(url: loadedFile)) + mediaProvider.loadFileFromSourceFilenameReturnValue = .success(MediaFileHandleProxy.unmanaged(url: loadedFile)) voiceMessageMediaManager = VoiceMessageMediaManager(mediaProvider: mediaProvider, voiceMessageCache: voiceMessageCache,