diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index dd92b0d7d..0aff21824 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -147,6 +147,7 @@ 208C19811613F9A10F8A7B75 /* MediaLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFCE895ECFFA53FEE64D62B /* MediaLoader.swift */; }; 20C16A3F718802B0E4A19C83 /* URLComponentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76310030C831D4610A705603 /* URLComponentsTests.swift */; }; 2118E35D312951B241067BD5 /* MessageComposerTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 345172AD4377E83A44BD864F /* MessageComposerTextField.swift */; }; + 211B5F524E851178EE549417 /* CurrentValuePublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 127C8472672A5BA09EF1ACF8 /* CurrentValuePublisher.swift */; }; 21813AF91CFC6F3E3896DB53 /* AppLockSetupBiometricsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10F130DF775CE6BC51A4E392 /* AppLockSetupBiometricsScreenModels.swift */; }; 21AFEFB8CEFE56A3811A1F5B /* VoiceMessageCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 283974987DA7EC61D2AB57D9 /* VoiceMessageCacheTests.swift */; }; 21BF2B7CEDFE3CA67C5355AD /* test_image.png in Resources */ = {isa = PBXBuildFile; fileRef = C733D11B421CFE3A657EF230 /* test_image.png */; }; @@ -341,6 +342,7 @@ 4D23D41B8109E010304050F8 /* QRCodeLoginScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA551A98778CEE7366838CE2 /* QRCodeLoginScreenCoordinator.swift */; }; 4D4D236F0BBCDC4D2CBCCBB5 /* RoomChangePermissionsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = C729D95CB4588D4D9AAC3DFA /* RoomChangePermissionsScreenModels.swift */; }; 4DAEE2468669848B6C9F55B4 /* TimelineReadReceiptsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33035418BB35754232985871 /* TimelineReadReceiptsView.swift */; }; + 4DEEFB73181C3B023DB42686 /* NetworkMonitorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1575947B7A6FE08C57FE5EE4 /* NetworkMonitorProtocol.swift */; }; 4E0D9E09B52CEC4C0E6211A8 /* MediaPickerScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F49FB9EE2913234F06CE68 /* MediaPickerScreenCoordinator.swift */; }; 4E36A66E0EDA74BF3A036FD0 /* RoomChangeRolesScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AAD8C633AA57948B34EDCF7 /* RoomChangeRolesScreenViewModelProtocol.swift */; }; 4E8A2A2CFEB212F14E49E1A1 /* AppLockSetupSettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5484457C81B325660901B161 /* AppLockSetupSettingsScreen.swift */; }; @@ -579,7 +581,6 @@ 84EFCB95F9DA2979C8042B26 /* UITestsSignalling.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7F0192CE2F891141A25B49F /* UITestsSignalling.swift */; }; 8544657DEEE717ED2E22E382 /* RoomNotificationSettingsProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5D1BAA90F3A073D91B4F16B /* RoomNotificationSettingsProxyMock.swift */; }; 854E82E064BA53CD0BC45600 /* LocationRoomTimelineItemContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6613DE16AD26B3A74DA1F5 /* LocationRoomTimelineItemContent.swift */; }; - 85813D87DDD7F67A46BD9AF7 /* ImageProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7E8A8047B50E3607ACD354E /* ImageProviderProtocol.swift */; }; 858276B19C7C0AD4CA98EA78 /* portrait_test_image.jpg in Resources */ = {isa = PBXBuildFile; fileRef = AF042B0FB2EE88977C91E330 /* portrait_test_image.jpg */; }; 8587A53DE8EF94FD796DC375 /* RoomAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEF5FE93A06F563B477F024A /* RoomAvatarImage.swift */; }; 859E2CA2EDF343BD24DE52EB /* RoomDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6404166CBF5CC88673FF9E2 /* RoomDetails.swift */; }; @@ -656,6 +657,7 @@ 94A65DD8A353DF112EBEF67A /* SessionVerificationControllerProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D56469A9EE0CFA2B7BA9760 /* SessionVerificationControllerProxyProtocol.swift */; }; 94D0F36A87E596A93C0C178A /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E89E530A8E92EC44301CA1 /* Bundle.swift */; }; 94E15D018D70563FA4AB4E5A /* ComposerToolbarModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D3A7375AB22721C436EB056 /* ComposerToolbarModels.swift */; }; + 94F0B78928E952689ACDB271 /* NetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D67E616BCA82D8A1258D488 /* NetworkMonitor.swift */; }; 95690DDD9D547D3D842ACBE3 /* AnalyticsSettingsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD371B60E07A5324B9507EF /* AnalyticsSettingsScreenCoordinator.swift */; }; 9586E90A447C4896C0CA3A8E /* TimelineItemReplyDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE89A8BD65CCE3FCC925CA14 /* TimelineItemReplyDetails.swift */; }; 95E7B236F7116CACE05A6BC9 /* BlockedUsersScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A16D0F226B1819D017531647 /* BlockedUsersScreenCoordinator.swift */; }; @@ -721,7 +723,6 @@ A37EED79941AD3B7140B3822 /* UIDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287FC98AF2664EAD79C0D902 /* UIDevice.swift */; }; A3A7A05E8F9B7EB0E1A09A2A /* SoftLogoutScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05596E4A11A8C9346E9E54AE /* SoftLogoutScreenCoordinator.swift */; }; A3D7110C1E75E7B4A73BE71C /* VoiceMessageRecorderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93C94C30E3135BC9290DE13 /* VoiceMessageRecorderTests.swift */; }; - A3E390675E9730C176B59E1B /* ImageProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7E8A8047B50E3607ACD354E /* ImageProviderProtocol.swift */; }; A439B456D0761D6541745CC3 /* NSRegularExpresion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95BAC0F6C9644336E9567EE6 /* NSRegularExpresion.swift */; }; A440D4BC02088482EC633A88 /* KeychainControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E94DCFEE803E5ABAE8ACCE /* KeychainControllerProtocol.swift */; }; A494741843F087881299ACF0 /* RestorationToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3558A15CFB934F9229301527 /* RestorationToken.swift */; }; @@ -743,7 +744,6 @@ A808DC3F72D15C6C5A52317E /* TimelineItemDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCDA016D05107DED3B9495CB /* TimelineItemDebugView.swift */; }; A816F7087C495D85048AC50E /* RoomMemberDetailsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B6E30BB748F3F480F077969 /* RoomMemberDetailsScreenModels.swift */; }; A851635B3255C6DC07034A12 /* RoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8108C8F0ACF6A7EB72D0117 /* RoomScreenCoordinator.swift */; }; - A896998A6784DB6F16E912F4 /* MockMediaLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AB7D7DAAAF662DED9D02379 /* MockMediaLoader.swift */; }; A8FA7671948E3DF27F320026 /* BugReportFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7367B3B9A8CAF902220F31D1 /* BugReportFlowCoordinator.swift */; }; A93661C962B12942C08864B6 /* Prefire in Frameworks */ = {isa = PBXBuildFile; productRef = 2629CF48B33643CD5F69C612 /* Prefire */; }; A9482B967FC85DA611514D35 /* VoiceMessageRoomPlaybackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCD41CD67DB5DA0D436BFE9 /* VoiceMessageRoomPlaybackView.swift */; }; @@ -1504,7 +1504,6 @@ 49E751D7EDB6043238111D90 /* UNNotificationRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNNotificationRequest.swift; sourceTree = ""; }; 4A2B5274C1D3D2999D643786 /* EncryptionResetPasswordScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetPasswordScreenViewModelProtocol.swift; sourceTree = ""; }; 4A5B4CD611DE7E94F5BA87B2 /* AppLockTimerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockTimerTests.swift; sourceTree = ""; }; - 4AB7D7DAAAF662DED9D02379 /* MockMediaLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockMediaLoader.swift; sourceTree = ""; }; 4B2D4EEBE8C098BBADD10939 /* SecureBackupKeyBackupScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupKeyBackupScreenCoordinator.swift; sourceTree = ""; }; 4B41FABA2B0AEF4389986495 /* LoginMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginMode.swift; sourceTree = ""; }; 4BD371B60E07A5324B9507EF /* AnalyticsSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenCoordinator.swift; sourceTree = ""; }; @@ -2221,7 +2220,6 @@ F733F135E6D67BBBEB76CC30 /* AppLockUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockUITests.swift; sourceTree = ""; }; F74532E01B317C56C1BE8FA8 /* RoomTimelineProviderMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineProviderMock.swift; sourceTree = ""; }; F7478623CECC9438014244BA /* ServerConfirmationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreen.swift; sourceTree = ""; }; - F7E8A8047B50E3607ACD354E /* ImageProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageProviderProtocol.swift; sourceTree = ""; }; F875D71347DC81EAE7687446 /* NavigationRootCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationRootCoordinatorTests.swift; sourceTree = ""; }; F899D02CF26EA7675EEBE74C /* UserSessionScreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionScreenTests.swift; sourceTree = ""; }; F8CCF9A924521DECA44778C4 /* AppLockSetupBiometricsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupBiometricsScreen.swift; sourceTree = ""; }; @@ -3848,7 +3846,6 @@ 3EF1AC723C2609C7705569CA /* MediaLoaderTests.swift */, 62A81CCC2516D9CF9322DF01 /* MediaProviderTests.swift */, AC1DA29A5A041CC0BACA7CB0 /* MockImageCache.swift */, - 4AB7D7DAAAF662DED9D02379 /* MockMediaLoader.swift */, ); path = MediaProvider; sourceTree = ""; @@ -3922,6 +3919,7 @@ 5221DFDF809142A2D6AC82B9 /* RoomScreen.swift */, 4552D3466B1453F287223ADA /* SwipeRightAction.swift */, 464C6BFAA853DC755B9C1F60 /* PinnedItemsBanner */, + B7D3886505ECC85A06DA8258 /* Timeline */, ); path = View; sourceTree = ""; @@ -4701,6 +4699,13 @@ path = UserIndicator; sourceTree = ""; }; + B7D3886505ECC85A06DA8258 /* Timeline */ = { + isa = PBXGroup; + children = ( + ); + path = Timeline; + sourceTree = ""; + }; B86CF59E083C82C2A842E4AD /* RoomMemberDetailsScreen */ = { isa = PBXGroup; children = ( @@ -4873,7 +4878,6 @@ CA15BB3F6C62B35AE2C281A9 /* Provider */ = { isa = PBXGroup; children = ( - F7E8A8047B50E3607ACD354E /* ImageProviderProtocol.swift */, DEC1D382565A4E9CAC2F14EA /* MediaFileHandleProxy.swift */, 8AFCE895ECFFA53FEE64D62B /* MediaLoader.swift */, 1A02406480C351B8C6E0682C /* MediaLoaderProtocol.swift */, @@ -5877,6 +5881,7 @@ 9A3B0CDF097E3838FB1B9595 /* Bundle.swift in Sources */, 238D561CA231339C6D4D06F3 /* ClientBuilder.swift in Sources */, 0BAF83521871E69D222EE8E4 /* ClientBuilderHook.swift in Sources */, + 211B5F524E851178EE549417 /* CurrentValuePublisher.swift in Sources */, B5618E3C948584E5C1F67033 /* DTHTMLElement+AttributedStringBuilder.swift in Sources */, DFCA89C4EC2A5332ED6B441F /* DataProtectionManager.swift in Sources */, 24A75F72EEB7561B82D726FD /* Date.swift in Sources */, @@ -5884,7 +5889,6 @@ CFEC53440C572CEEABC4A6A0 /* ElementXAttributeScope.swift in Sources */, A33784831AD880A670CAA9F9 /* FileManager.swift in Sources */, 59F940FCBE6BC343AECEF75E /* ImageCache.swift in Sources */, - A3E390675E9730C176B59E1B /* ImageProviderProtocol.swift in Sources */, EBE13FAB4E29738AC41BD3E5 /* InfoPlistReader.swift in Sources */, 8691186F9B99BCDDB7CACDD8 /* KeychainController.swift in Sources */, A440D4BC02088482EC633A88 /* KeychainControllerProtocol.swift in Sources */, @@ -5902,6 +5906,8 @@ 6AECC84BE14A13440120FED8 /* NSESettingsProtocol.swift in Sources */, 30CC4F796B27BE8B1DFDBF5A /* NSEUserSession.swift in Sources */, 1D5DC685CED904386C89B7DA /* NSRegularExpresion.swift in Sources */, + 94F0B78928E952689ACDB271 /* NetworkMonitor.swift in Sources */, + 4DEEFB73181C3B023DB42686 /* NetworkMonitorProtocol.swift in Sources */, 5C02841B2A86327B2C377682 /* NotificationConstants.swift in Sources */, 2689D22EF1D10D22B0A4DAEA /* NotificationContentBuilder.swift in Sources */, 5D70FAE4D2BF4553AFFFFE41 /* NotificationItemProxy.swift in Sources */, @@ -5987,7 +5993,6 @@ F777C6FEE7D106136E2ED2B2 /* MessageForwardingScreenViewModelTests.swift in Sources */, 1146E9EDCF8344F7D6E0D553 /* MockCoder.swift in Sources */, DC68E866D6E664B0D2B06E74 /* MockImageCache.swift in Sources */, - A896998A6784DB6F16E912F4 /* MockMediaLoader.swift in Sources */, 981853650217B6C8ECDD998C /* NavigationRootCoordinatorTests.swift in Sources */, 69C7B956B74BEC3DB88224EA /* NavigationSplitCoordinatorTests.swift in Sources */, 4BB282209EA82015D0DF8F89 /* NavigationStackCoordinatorTests.swift in Sources */, @@ -6332,7 +6337,6 @@ 01681E8B20AD6F0D237F2DC1 /* IdentityConfirmedScreenViewModel.swift in Sources */, AADE7C2497A7B55D8BED7BD6 /* IdentityConfirmedScreenViewModelProtocol.swift in Sources */, BA31448FBD9697F8CB9A83CD /* ImageCache.swift in Sources */, - 85813D87DDD7F67A46BD9AF7 /* ImageProviderProtocol.swift in Sources */, 7CD16990BA843BE9ED639129 /* ImageRoomTimelineItem.swift in Sources */, B796A25F282C0A340D1B9C12 /* ImageRoomTimelineItemContent.swift in Sources */, E2D57361B835E4D2230960E6 /* ImageRoomTimelineView.swift in Sources */, diff --git a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift index e052050fe..9d98247de 100644 --- a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift @@ -666,7 +666,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { private func presentRoomDirectorySearch() { let coordinator = RoomDirectorySearchScreenCoordinator(parameters: .init(clientProxy: userSession.clientProxy, - imageProvider: userSession.mediaProvider, + mediaProvider: userSession.mediaProvider, userIndicatorController: ServiceLocator.shared.userIndicatorController)) coordinator.actionsPublisher.sink { [weak self] action in diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index aa7eb4581..08780c2e1 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -5816,6 +5816,231 @@ class KeychainControllerMock: KeychainControllerProtocol { removePINCodeBiometricStateClosure?() } } +class MediaLoaderMock: MediaLoaderProtocol { + + //MARK: - loadMediaContentForSource + + var loadMediaContentForSourceThrowableError: Error? + var loadMediaContentForSourceUnderlyingCallsCount = 0 + var loadMediaContentForSourceCallsCount: Int { + get { + if Thread.isMainThread { + return loadMediaContentForSourceUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = loadMediaContentForSourceUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + loadMediaContentForSourceUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + loadMediaContentForSourceUnderlyingCallsCount = newValue + } + } + } + } + var loadMediaContentForSourceCalled: Bool { + return loadMediaContentForSourceCallsCount > 0 + } + var loadMediaContentForSourceReceivedSource: MediaSourceProxy? + var loadMediaContentForSourceReceivedInvocations: [MediaSourceProxy] = [] + + var loadMediaContentForSourceUnderlyingReturnValue: Data! + var loadMediaContentForSourceReturnValue: Data! { + get { + if Thread.isMainThread { + return loadMediaContentForSourceUnderlyingReturnValue + } else { + var returnValue: Data? = nil + DispatchQueue.main.sync { + returnValue = loadMediaContentForSourceUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + loadMediaContentForSourceUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + loadMediaContentForSourceUnderlyingReturnValue = newValue + } + } + } + } + var loadMediaContentForSourceClosure: ((MediaSourceProxy) async throws -> Data)? + + func loadMediaContentForSource(_ source: MediaSourceProxy) async throws -> Data { + if let error = loadMediaContentForSourceThrowableError { + throw error + } + loadMediaContentForSourceCallsCount += 1 + loadMediaContentForSourceReceivedSource = source + DispatchQueue.main.async { + self.loadMediaContentForSourceReceivedInvocations.append(source) + } + if let loadMediaContentForSourceClosure = loadMediaContentForSourceClosure { + return try await loadMediaContentForSourceClosure(source) + } else { + return loadMediaContentForSourceReturnValue + } + } + //MARK: - loadMediaThumbnailForSource + + var loadMediaThumbnailForSourceWidthHeightThrowableError: Error? + var loadMediaThumbnailForSourceWidthHeightUnderlyingCallsCount = 0 + var loadMediaThumbnailForSourceWidthHeightCallsCount: Int { + get { + if Thread.isMainThread { + return loadMediaThumbnailForSourceWidthHeightUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = loadMediaThumbnailForSourceWidthHeightUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + loadMediaThumbnailForSourceWidthHeightUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + loadMediaThumbnailForSourceWidthHeightUnderlyingCallsCount = newValue + } + } + } + } + var loadMediaThumbnailForSourceWidthHeightCalled: Bool { + return loadMediaThumbnailForSourceWidthHeightCallsCount > 0 + } + var loadMediaThumbnailForSourceWidthHeightReceivedArguments: (source: MediaSourceProxy, width: UInt, height: UInt)? + var loadMediaThumbnailForSourceWidthHeightReceivedInvocations: [(source: MediaSourceProxy, width: UInt, height: UInt)] = [] + + var loadMediaThumbnailForSourceWidthHeightUnderlyingReturnValue: Data! + var loadMediaThumbnailForSourceWidthHeightReturnValue: Data! { + get { + if Thread.isMainThread { + return loadMediaThumbnailForSourceWidthHeightUnderlyingReturnValue + } else { + var returnValue: Data? = nil + DispatchQueue.main.sync { + returnValue = loadMediaThumbnailForSourceWidthHeightUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + loadMediaThumbnailForSourceWidthHeightUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + loadMediaThumbnailForSourceWidthHeightUnderlyingReturnValue = newValue + } + } + } + } + var loadMediaThumbnailForSourceWidthHeightClosure: ((MediaSourceProxy, UInt, UInt) async throws -> Data)? + + func loadMediaThumbnailForSource(_ source: MediaSourceProxy, width: UInt, height: UInt) async throws -> Data { + if let error = loadMediaThumbnailForSourceWidthHeightThrowableError { + throw error + } + loadMediaThumbnailForSourceWidthHeightCallsCount += 1 + loadMediaThumbnailForSourceWidthHeightReceivedArguments = (source: source, width: width, height: height) + DispatchQueue.main.async { + self.loadMediaThumbnailForSourceWidthHeightReceivedInvocations.append((source: source, width: width, height: height)) + } + if let loadMediaThumbnailForSourceWidthHeightClosure = loadMediaThumbnailForSourceWidthHeightClosure { + return try await loadMediaThumbnailForSourceWidthHeightClosure(source, width, height) + } else { + return loadMediaThumbnailForSourceWidthHeightReturnValue + } + } + //MARK: - loadMediaFileForSource + + var loadMediaFileForSourceBodyThrowableError: Error? + var loadMediaFileForSourceBodyUnderlyingCallsCount = 0 + var loadMediaFileForSourceBodyCallsCount: Int { + get { + if Thread.isMainThread { + return loadMediaFileForSourceBodyUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = loadMediaFileForSourceBodyUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + loadMediaFileForSourceBodyUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + loadMediaFileForSourceBodyUnderlyingCallsCount = newValue + } + } + } + } + var loadMediaFileForSourceBodyCalled: Bool { + return loadMediaFileForSourceBodyCallsCount > 0 + } + var loadMediaFileForSourceBodyReceivedArguments: (source: MediaSourceProxy, body: String?)? + var loadMediaFileForSourceBodyReceivedInvocations: [(source: MediaSourceProxy, body: String?)] = [] + + var loadMediaFileForSourceBodyUnderlyingReturnValue: MediaFileHandleProxy! + var loadMediaFileForSourceBodyReturnValue: MediaFileHandleProxy! { + get { + if Thread.isMainThread { + return loadMediaFileForSourceBodyUnderlyingReturnValue + } else { + var returnValue: MediaFileHandleProxy? = nil + DispatchQueue.main.sync { + returnValue = loadMediaFileForSourceBodyUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + loadMediaFileForSourceBodyUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + loadMediaFileForSourceBodyUnderlyingReturnValue = newValue + } + } + } + } + var loadMediaFileForSourceBodyClosure: ((MediaSourceProxy, String?) async throws -> MediaFileHandleProxy)? + + func loadMediaFileForSource(_ source: MediaSourceProxy, body: String?) async throws -> MediaFileHandleProxy { + if let error = loadMediaFileForSourceBodyThrowableError { + throw error + } + loadMediaFileForSourceBodyCallsCount += 1 + loadMediaFileForSourceBodyReceivedArguments = (source: source, body: body) + DispatchQueue.main.async { + self.loadMediaFileForSourceBodyReceivedInvocations.append((source: source, body: body)) + } + if let loadMediaFileForSourceBodyClosure = loadMediaFileForSourceBodyClosure { + return try await loadMediaFileForSourceBodyClosure(source, body) + } else { + return loadMediaFileForSourceBodyReturnValue + } + } +} class MediaPlayerMock: MediaPlayerProtocol { var mediaSource: MediaSourceProxy? var duration: TimeInterval { diff --git a/ElementX/Sources/Other/Extensions/ClientBuilder.swift b/ElementX/Sources/Other/Extensions/ClientBuilder.swift index 635f9acb8..07525e3be 100644 --- a/ElementX/Sources/Other/Extensions/ClientBuilder.swift +++ b/ElementX/Sources/Other/Extensions/ClientBuilder.swift @@ -29,6 +29,7 @@ extension ClientBuilder { .slidingSyncProxy(slidingSyncProxy: slidingSyncProxy?.absoluteString) .enableCrossProcessRefreshLock(processId: InfoPlistReader.main.bundleIdentifier, sessionDelegate: sessionDelegate) .userAgent(userAgent: UserAgentBuilder.makeASCIIUserAgent()) + .requestConfig(config: .init(retryLimit: 0, timeout: 15000, maxConcurrentRequests: nil, retryTimeout: nil)) builder = switch slidingSync { case .restored: builder diff --git a/ElementX/Sources/Other/Pills/PillAttachmentViewProvider.swift b/ElementX/Sources/Other/Pills/PillAttachmentViewProvider.swift index 35cd4bfdb..bbd07f70e 100644 --- a/ElementX/Sources/Other/Pills/PillAttachmentViewProvider.swift +++ b/ElementX/Sources/Other/Pills/PillAttachmentViewProvider.swift @@ -51,20 +51,20 @@ final class PillAttachmentViewProvider: NSTextAttachmentViewProvider, NSSecureCo } let context: PillContext - let imageProvider: ImageProviderProtocol? + let mediaProvider: MediaProviderProtocol? if ProcessInfo.isXcodePreview || ProcessInfo.isRunningTests { // The mock viewModel simulates the loading logic for testing purposes context = PillContext.mock(type: .loadUser(isOwn: false)) - imageProvider = MockMediaProvider() + mediaProvider = MockMediaProvider() } else if let timelineContext = delegate?.timelineContext { context = PillContext(timelineContext: timelineContext, data: pillData) - imageProvider = timelineContext.imageProvider + mediaProvider = timelineContext.mediaProvider } else { MXLog.failure("[PillAttachmentViewProvider]: missing room context") return } - let view = PillView(imageProvider: imageProvider, context: context) { [weak self] in + let view = PillView(mediaProvider: mediaProvider, context: context) { [weak self] in self?.delegate?.invalidateTextAttachmentsDisplay() } let controller = UIHostingController(rootView: view) diff --git a/ElementX/Sources/Other/Pills/PillView.swift b/ElementX/Sources/Other/Pills/PillView.swift index a846f7f4a..690bd8c2d 100644 --- a/ElementX/Sources/Other/Pills/PillView.swift +++ b/ElementX/Sources/Other/Pills/PillView.swift @@ -17,7 +17,7 @@ import SwiftUI struct PillView: View { - let imageProvider: ImageProviderProtocol? + let mediaProvider: MediaProviderProtocol? @ObservedObject var context: PillContext /// callback triggerd by changes in the display text let didChangeText: () -> Void @@ -49,23 +49,23 @@ struct PillView_Previews: PreviewProvider, TestablePreview { static let mockMediaProvider = MockMediaProvider() static var previews: some View { - PillView(imageProvider: mockMediaProvider, + PillView(mediaProvider: mockMediaProvider, context: PillContext.mock(type: .loadUser(isOwn: false))) { } .frame(maxWidth: PillConstants.mockMaxWidth) .previewDisplayName("Loading") - PillView(imageProvider: mockMediaProvider, + PillView(mediaProvider: mockMediaProvider, context: PillContext.mock(type: .loadUser(isOwn: true))) { } .frame(maxWidth: PillConstants.mockMaxWidth) .previewDisplayName("Loading Own") - PillView(imageProvider: mockMediaProvider, + PillView(mediaProvider: mockMediaProvider, context: PillContext.mock(type: .loadedUser(isOwn: false))) { } .frame(maxWidth: PillConstants.mockMaxWidth) .previewDisplayName("Loaded Long") - PillView(imageProvider: mockMediaProvider, + PillView(mediaProvider: mockMediaProvider, context: PillContext.mock(type: .loadedUser(isOwn: true))) { } .frame(maxWidth: PillConstants.mockMaxWidth) .previewDisplayName("Loaded Long Own") - PillView(imageProvider: mockMediaProvider, + PillView(mediaProvider: mockMediaProvider, context: PillContext.mock(type: .allUsers)) { } .frame(maxWidth: PillConstants.mockMaxWidth) .previewDisplayName("All Users") diff --git a/ElementX/Sources/Other/SwiftUI/ViewModel/StateStoreViewModel.swift b/ElementX/Sources/Other/SwiftUI/ViewModel/StateStoreViewModel.swift index 861f9a55f..99ca29e9b 100644 --- a/ElementX/Sources/Other/SwiftUI/ViewModel/StateStoreViewModel.swift +++ b/ElementX/Sources/Other/SwiftUI/ViewModel/StateStoreViewModel.swift @@ -38,8 +38,8 @@ class StateStoreViewModel { set { context.viewState = newValue } } - init(initialViewState: State, imageProvider: ImageProviderProtocol? = nil) { - context = Context(initialViewState: initialViewState, imageProvider: imageProvider) + init(initialViewState: State, mediaProvider: MediaProviderProtocol? = nil) { + context = Context(initialViewState: initialViewState, mediaProvider: mediaProvider) context.viewModel = self } @@ -73,7 +73,7 @@ class StateStoreViewModel { /// An optional image loading service so that views can manage themselves /// Intentionally non-generic so that it doesn't grow uncontrollably - let imageProvider: ImageProviderProtocol? + let mediaProvider: MediaProviderProtocol? /// Set-able/Bindable access to the bindable state. subscript(dynamicMember keyPath: WritableKeyPath) -> T { @@ -87,9 +87,9 @@ class StateStoreViewModel { viewModel?.process(viewAction: viewAction) } - fileprivate init(initialViewState: State, imageProvider: ImageProviderProtocol?) { + fileprivate init(initialViewState: State, mediaProvider: MediaProviderProtocol?) { self.viewState = initialViewState - self.imageProvider = imageProvider + self.mediaProvider = mediaProvider } } } diff --git a/ElementX/Sources/Other/SwiftUI/Views/AvatarHeaderView.swift b/ElementX/Sources/Other/SwiftUI/Views/AvatarHeaderView.swift index 47ecc6356..14b487f64 100644 --- a/ElementX/Sources/Other/SwiftUI/Views/AvatarHeaderView.swift +++ b/ElementX/Sources/Other/SwiftUI/Views/AvatarHeaderView.swift @@ -33,13 +33,13 @@ struct AvatarHeaderView: View { private let badges: [Badge] private let avatarSize: AvatarSize - private let imageProvider: ImageProviderProtocol? + private let mediaProvider: MediaProviderProtocol? private var onAvatarTap: (() -> Void)? @ViewBuilder private var footer: () -> Footer init(room: RoomDetails, avatarSize: AvatarSize, - imageProvider: ImageProviderProtocol? = nil, + mediaProvider: MediaProviderProtocol? = nil, onAvatarTap: (() -> Void)? = nil, @ViewBuilder footer: @escaping () -> Footer) { avatarInfo = .room(room.avatar) @@ -47,7 +47,7 @@ struct AvatarHeaderView: View { subtitle = room.canonicalAlias self.avatarSize = avatarSize - self.imageProvider = imageProvider + self.mediaProvider = mediaProvider self.onAvatarTap = onAvatarTap self.footer = footer @@ -61,7 +61,7 @@ struct AvatarHeaderView: View { init(accountOwner: RoomMemberDetails, dmRecipient: RoomMemberDetails, - imageProvider: ImageProviderProtocol? = nil, + mediaProvider: MediaProviderProtocol? = nil, onAvatarTap: (() -> Void)? = nil, @ViewBuilder footer: @escaping () -> Footer) { let dmRecipientProfile = UserProfileProxy(member: dmRecipient) @@ -70,7 +70,7 @@ struct AvatarHeaderView: View { subtitle = dmRecipientProfile.displayName == nil ? nil : dmRecipientProfile.userID avatarSize = .user(on: .dmDetails) - self.imageProvider = imageProvider + self.mediaProvider = mediaProvider self.onAvatarTap = onAvatarTap self.footer = footer // In EL-X a DM is by definition always encrypted @@ -79,21 +79,21 @@ struct AvatarHeaderView: View { init(member: RoomMemberDetails, avatarSize: AvatarSize, - imageProvider: ImageProviderProtocol? = nil, + mediaProvider: MediaProviderProtocol? = nil, onAvatarTap: (() -> Void)? = nil, @ViewBuilder footer: @escaping () -> Footer) { let profile = UserProfileProxy(member: member) self.init(user: profile, avatarSize: avatarSize, - imageProvider: imageProvider, + mediaProvider: mediaProvider, onAvatarTap: onAvatarTap, footer: footer) } init(user: UserProfileProxy, avatarSize: AvatarSize, - imageProvider: ImageProviderProtocol? = nil, + mediaProvider: MediaProviderProtocol? = nil, onAvatarTap: (() -> Void)? = nil, @ViewBuilder footer: @escaping () -> Footer) { avatarInfo = .user(user) @@ -101,7 +101,7 @@ struct AvatarHeaderView: View { subtitle = user.displayName == nil ? nil : user.userID self.avatarSize = avatarSize - self.imageProvider = imageProvider + self.mediaProvider = mediaProvider self.onAvatarTap = onAvatarTap self.footer = footer badges = [] @@ -136,13 +136,13 @@ struct AvatarHeaderView: View { case .room(let roomAvatar): RoomAvatarImage(avatar: roomAvatar, avatarSize: avatarSize, - imageProvider: imageProvider) + mediaProvider: mediaProvider) case .user(let userProfile): LoadableAvatarImage(url: userProfile.avatarURL, name: userProfile.displayName, contentID: userProfile.userID, avatarSize: avatarSize, - imageProvider: imageProvider) + mediaProvider: mediaProvider) } } @@ -199,7 +199,7 @@ struct AvatarHeaderView_Previews: PreviewProvider, TestablePreview { isEncrypted: true, isPublic: true), avatarSize: .room(on: .details), - imageProvider: MockMediaProvider()) { + mediaProvider: MockMediaProvider()) { HStack(spacing: 32) { ShareLink(item: "test") { Image(systemName: "square.and.arrow.up") @@ -213,7 +213,7 @@ struct AvatarHeaderView_Previews: PreviewProvider, TestablePreview { Form { AvatarHeaderView(accountOwner: RoomMemberDetails(withProxy: RoomMemberProxyMock.mockMe), dmRecipient: RoomMemberDetails(withProxy: RoomMemberProxyMock.mockAlice), - imageProvider: MockMediaProvider()) { + mediaProvider: MockMediaProvider()) { HStack(spacing: 32) { ShareLink(item: "test") { Image(systemName: "square.and.arrow.up") @@ -228,11 +228,11 @@ struct AvatarHeaderView_Previews: PreviewProvider, TestablePreview { VStack(spacing: 16) { AvatarHeaderView(member: RoomMemberDetails(withProxy: RoomMemberProxyMock.mockAlice), avatarSize: .room(on: .details), - imageProvider: MockMediaProvider()) { Text("") } + mediaProvider: MockMediaProvider()) { Text("") } AvatarHeaderView(member: RoomMemberDetails(withProxy: RoomMemberProxyMock.mockBanned[3]), avatarSize: .room(on: .details), - imageProvider: MockMediaProvider()) { Text("") } + mediaProvider: MockMediaProvider()) { Text("") } } .padding() .background(Color.compound.bgSubtleSecondaryLevel0) diff --git a/ElementX/Sources/Other/SwiftUI/Views/LoadableAvatarImage.swift b/ElementX/Sources/Other/SwiftUI/Views/LoadableAvatarImage.swift index 06d4972fe..7ce129e8f 100644 --- a/ElementX/Sources/Other/SwiftUI/Views/LoadableAvatarImage.swift +++ b/ElementX/Sources/Other/SwiftUI/Views/LoadableAvatarImage.swift @@ -21,16 +21,16 @@ struct LoadableAvatarImage: View { private let name: String? private let contentID: String? private let avatarSize: AvatarSize - private let imageProvider: ImageProviderProtocol? + private let mediaProvider: MediaProviderProtocol? @ScaledMetric private var frameSize: CGFloat - init(url: URL?, name: String?, contentID: String?, avatarSize: AvatarSize, imageProvider: ImageProviderProtocol?) { + init(url: URL?, name: String?, contentID: String?, avatarSize: AvatarSize, mediaProvider: MediaProviderProtocol?) { self.url = url self.name = name self.contentID = contentID self.avatarSize = avatarSize - self.imageProvider = imageProvider + self.mediaProvider = mediaProvider _frameSize = ScaledMetric(wrappedValue: avatarSize.value) } @@ -48,7 +48,7 @@ struct LoadableAvatarImage: View { LoadableImage(url: url, mediaType: .avatar, size: avatarSize.scaledSize, - imageProvider: imageProvider) { image in + mediaProvider: mediaProvider) { image in image .scaledToFill() } placeholder: { diff --git a/ElementX/Sources/Other/SwiftUI/Views/LoadableImage.swift b/ElementX/Sources/Other/SwiftUI/Views/LoadableImage.swift index 7ae8f09bc..e574b31cf 100644 --- a/ElementX/Sources/Other/SwiftUI/Views/LoadableImage.swift +++ b/ElementX/Sources/Other/SwiftUI/Views/LoadableImage.swift @@ -14,6 +14,7 @@ // limitations under the License. // +import Combine import Kingfisher import SwiftUI @@ -28,7 +29,7 @@ struct LoadableImage: View { private let mediaType: LoadableImageMediaType private let blurhash: String? private let size: CGSize? - private let imageProvider: ImageProviderProtocol? + private let mediaProvider: MediaProviderProtocol? private let transformer: (AnyView) -> TransformerView private let placeholder: () -> PlaceholderView @@ -44,14 +45,14 @@ struct LoadableImage: View { mediaType: LoadableImageMediaType = .generic, blurhash: String? = nil, size: CGSize? = nil, - imageProvider: ImageProviderProtocol?, + mediaProvider: MediaProviderProtocol?, transformer: @escaping (AnyView) -> TransformerView = { $0 }, placeholder: @escaping () -> PlaceholderView) { self.mediaSource = mediaSource self.mediaType = mediaType self.blurhash = blurhash self.size = size - self.imageProvider = imageProvider + self.mediaProvider = mediaProvider self.transformer = transformer self.placeholder = placeholder } @@ -60,14 +61,14 @@ struct LoadableImage: View { mediaType: LoadableImageMediaType = .generic, blurhash: String? = nil, size: CGSize? = nil, - imageProvider: ImageProviderProtocol?, + mediaProvider: MediaProviderProtocol?, transformer: @escaping (AnyView) -> TransformerView = { $0 }, placeholder: @escaping () -> PlaceholderView) { self.init(mediaSource: MediaSourceProxy(url: url, mimeType: nil), mediaType: mediaType, blurhash: blurhash, size: size, - imageProvider: imageProvider, + mediaProvider: mediaProvider, transformer: transformer, placeholder: placeholder) } @@ -77,7 +78,7 @@ struct LoadableImage: View { mediaType: mediaType, blurhash: blurhash, size: size, - imageProvider: imageProvider, + mediaProvider: mediaProvider, transformer: transformer, placeholder: placeholder) // Binds the lifecycle of the LoadableImage to the associated URL. @@ -99,28 +100,20 @@ private struct LoadableImageContent TransformerView, placeholder: @escaping () -> PlaceholderView) { - assert(imageProvider != nil, "Missing image provider, make sure one has been supplied to the view model.") + assert(mediaProvider != nil, "Missing image provider, make sure one has been supplied to the view model.") self.mediaSource = mediaSource self.mediaType = mediaType self.blurhash = blurhash self.transformer = transformer self.placeholder = placeholder - _contentLoader = StateObject(wrappedValue: ContentLoader(mediaSource: mediaSource, size: size, imageProvider: imageProvider)) + _contentLoader = StateObject(wrappedValue: ContentLoader(mediaSource: mediaSource, size: size, mediaProvider: mediaProvider)) } var body: some View { - let _ = Task { - guard contentLoader.content == nil else { - return - } - - await contentLoader.load() - } - ZStack { switch contentLoader.content { case .image(let image): @@ -140,6 +133,20 @@ private struct LoadableImageContent.Kind @@ -52,7 +52,7 @@ struct UserProfileListRow: View { name: user.displayName, contentID: user.userID, avatarSize: .user(on: .startChat), - imageProvider: imageProvider) + mediaProvider: mediaProvider) } } @@ -74,21 +74,21 @@ struct UserProfileCell_Previews: PreviewProvider, TestablePreview { static var previews: some View { Form { - UserProfileListRow(user: .mockAlice, membership: nil, imageProvider: MockMediaProvider(), + UserProfileListRow(user: .mockAlice, membership: nil, mediaProvider: MockMediaProvider(), kind: .multiSelection(isSelected: true, action: action)) - UserProfileListRow(user: .mockBob, membership: nil, imageProvider: MockMediaProvider(), + UserProfileListRow(user: .mockBob, membership: nil, mediaProvider: MockMediaProvider(), kind: .multiSelection(isSelected: false, action: action)) - UserProfileListRow(user: .mockCharlie, membership: .join, imageProvider: MockMediaProvider(), + UserProfileListRow(user: .mockCharlie, membership: .join, mediaProvider: MockMediaProvider(), kind: .multiSelection(isSelected: true, action: action)) .disabled(true) - UserProfileListRow(user: .init(userID: "@someone:matrix.org"), membership: .join, imageProvider: MockMediaProvider(), + UserProfileListRow(user: .init(userID: "@someone:matrix.org"), membership: .join, mediaProvider: MockMediaProvider(), kind: .multiSelection(isSelected: false, action: action)) .disabled(true) - UserProfileListRow(user: .init(userID: "@someone:matrix.org"), membership: nil, imageProvider: MockMediaProvider(), + UserProfileListRow(user: .init(userID: "@someone:matrix.org"), membership: nil, mediaProvider: MockMediaProvider(), kind: .multiSelection(isSelected: false, action: action)) } .compoundList() diff --git a/ElementX/Sources/Screens/BlockedUsersScreen/BlockedUsersScreenViewModel.swift b/ElementX/Sources/Screens/BlockedUsersScreen/BlockedUsersScreenViewModel.swift index e1724d6d9..07b36f3ca 100644 --- a/ElementX/Sources/Screens/BlockedUsersScreen/BlockedUsersScreenViewModel.swift +++ b/ElementX/Sources/Screens/BlockedUsersScreen/BlockedUsersScreenViewModel.swift @@ -35,7 +35,7 @@ class BlockedUsersScreenViewModel: BlockedUsersScreenViewModelType, BlockedUsers let ignoredUsers = clientProxy.ignoredUsersPublisher.value?.map { UserProfileProxy(userID: $0) } super.init(initialViewState: BlockedUsersScreenViewState(blockedUsers: ignoredUsers ?? []), - imageProvider: mediaProvider) + mediaProvider: mediaProvider) showLoadingIndicator() diff --git a/ElementX/Sources/Screens/BlockedUsersScreen/View/BlockedUsersScreen.swift b/ElementX/Sources/Screens/BlockedUsersScreen/View/BlockedUsersScreen.swift index ce456d8b8..c020533c0 100644 --- a/ElementX/Sources/Screens/BlockedUsersScreen/View/BlockedUsersScreen.swift +++ b/ElementX/Sources/Screens/BlockedUsersScreen/View/BlockedUsersScreen.swift @@ -54,7 +54,7 @@ struct BlockedUsersScreen: View { name: user.displayName, contentID: user.userID, avatarSize: .user(on: .blockedUsers), - imageProvider: context.imageProvider) + mediaProvider: context.mediaProvider) .accessibilityHidden(true) } } diff --git a/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift b/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift index 4e6e35d58..0cf0888c1 100644 --- a/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift +++ b/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift @@ -45,7 +45,7 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol let bindings = CreateRoomViewStateBindings(roomName: parameters.name, roomTopic: parameters.topic, isRoomPrivate: parameters.isRoomPrivate) - super.init(initialViewState: CreateRoomViewState(selectedUsers: selectedUsers.value, bindings: bindings), imageProvider: userSession.mediaProvider) + super.init(initialViewState: CreateRoomViewState(selectedUsers: selectedUsers.value, bindings: bindings), mediaProvider: userSession.mediaProvider) createRoomParameters .map(\.avatarImageMedia) diff --git a/ElementX/Sources/Screens/CreateRoom/View/CreateRoomScreen.swift b/ElementX/Sources/Screens/CreateRoom/View/CreateRoomScreen.swift index 982d75f4b..e9ae22732 100644 --- a/ElementX/Sources/Screens/CreateRoom/View/CreateRoomScreen.swift +++ b/ElementX/Sources/Screens/CreateRoom/View/CreateRoomScreen.swift @@ -130,7 +130,7 @@ struct CreateRoomScreen: View { ScrollView(.horizontal, showsIndicators: false) { LazyHStack(spacing: 16) { ForEach(context.viewState.selectedUsers, id: \.userID) { user in - InviteUsersScreenSelectedItem(user: user, imageProvider: context.imageProvider) { + InviteUsersScreenSelectedItem(user: user, mediaProvider: context.mediaProvider) { context.send(viewAction: .deselectUser(user)) } .frame(width: invitedUserCellWidth) diff --git a/ElementX/Sources/Screens/GlobalSearchScreen/GlobalSearchScreenCoordinator.swift b/ElementX/Sources/Screens/GlobalSearchScreen/GlobalSearchScreenCoordinator.swift index 97c59adef..87f5a4731 100644 --- a/ElementX/Sources/Screens/GlobalSearchScreen/GlobalSearchScreenCoordinator.swift +++ b/ElementX/Sources/Screens/GlobalSearchScreen/GlobalSearchScreenCoordinator.swift @@ -41,7 +41,7 @@ class GlobalSearchScreenCoordinator: CoordinatorProtocol { init(parameters: GlobalSearchScreenCoordinatorParameters) { viewModel = GlobalSearchScreenViewModel(roomSummaryProvider: parameters.roomSummaryProvider, - imageProvider: parameters.mediaProvider) + mediaProvider: parameters.mediaProvider) viewModel.actions .sink { [weak self] action in diff --git a/ElementX/Sources/Screens/GlobalSearchScreen/GlobalSearchScreenViewModel.swift b/ElementX/Sources/Screens/GlobalSearchScreen/GlobalSearchScreenViewModel.swift index a64532fd8..b6b0ff9c1 100644 --- a/ElementX/Sources/Screens/GlobalSearchScreen/GlobalSearchScreenViewModel.swift +++ b/ElementX/Sources/Screens/GlobalSearchScreen/GlobalSearchScreenViewModel.swift @@ -28,11 +28,11 @@ class GlobalSearchScreenViewModel: GlobalSearchScreenViewModelType, GlobalSearch } init(roomSummaryProvider: RoomSummaryProviderProtocol, - imageProvider: ImageProviderProtocol) { + mediaProvider: MediaProviderProtocol) { self.roomSummaryProvider = roomSummaryProvider super.init(initialViewState: GlobalSearchScreenViewState(bindings: .init(searchQuery: "")), - imageProvider: imageProvider) + mediaProvider: mediaProvider) roomSummaryProvider.roomListPublisher .receive(on: DispatchQueue.main) diff --git a/ElementX/Sources/Screens/GlobalSearchScreen/View/GlobalSearchScreen.swift b/ElementX/Sources/Screens/GlobalSearchScreen/View/GlobalSearchScreen.swift index df81d231f..20151be5b 100644 --- a/ElementX/Sources/Screens/GlobalSearchScreen/View/GlobalSearchScreen.swift +++ b/ElementX/Sources/Screens/GlobalSearchScreen/View/GlobalSearchScreen.swift @@ -214,7 +214,7 @@ private class GlobalSearchTextField: UITextField { struct GlobalSearchScreen_Previews: PreviewProvider, TestablePreview { static let viewModel = GlobalSearchScreenViewModel(roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(.mockRooms))), - imageProvider: MockMediaProvider()) + mediaProvider: MockMediaProvider()) static var previews: some View { NavigationStack { diff --git a/ElementX/Sources/Screens/GlobalSearchScreen/View/GlobalSearchScreenCell.swift b/ElementX/Sources/Screens/GlobalSearchScreen/View/GlobalSearchScreenCell.swift index f32aba0dc..e3792c8b6 100644 --- a/ElementX/Sources/Screens/GlobalSearchScreen/View/GlobalSearchScreenCell.swift +++ b/ElementX/Sources/Screens/GlobalSearchScreen/View/GlobalSearchScreenCell.swift @@ -37,7 +37,7 @@ struct GlobalSearchScreenListRow: View { if dynamicTypeSize < .accessibility3 { RoomAvatarImage(avatar: room.avatar, avatarSize: .room(on: .messageForwarding), - imageProvider: context.imageProvider) + mediaProvider: context.mediaProvider) .dynamicTypeSize(dynamicTypeSize < .accessibility1 ? dynamicTypeSize : .accessibility1) .accessibilityHidden(true) } @@ -46,7 +46,7 @@ struct GlobalSearchScreenListRow: View { struct GlobalSearchScreenListRow_Previews: PreviewProvider, TestablePreview { static let viewModel = GlobalSearchScreenViewModel(roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(.mockRooms))), - imageProvider: MockMediaProvider()) + mediaProvider: MockMediaProvider()) static var previews: some View { List { diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index ea3b631b7..57020416f 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -51,7 +51,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol roomSummaryProvider = userSession.clientProxy.roomSummaryProvider super.init(initialViewState: .init(userID: userSession.clientProxy.userID), - imageProvider: userSession.mediaProvider) + mediaProvider: userSession.mediaProvider) userSession.clientProxy.userAvatarURLPublisher .receive(on: DispatchQueue.main) diff --git a/ElementX/Sources/Screens/HomeScreen/View/BloomView.swift b/ElementX/Sources/Screens/HomeScreen/View/BloomView.swift index 6b27dcc8c..0293d42dd 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/BloomView.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/BloomView.swift @@ -38,6 +38,6 @@ struct BloomView: View { name: context.viewState.userDisplayName, contentID: context.viewState.userID, avatarSize: .custom(256), - imageProvider: context.imageProvider) + mediaProvider: context.mediaProvider) } } diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift index f556269ff..d6311af90 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift @@ -148,7 +148,7 @@ struct HomeScreen: View { name: context.viewState.userDisplayName, contentID: context.viewState.userID, avatarSize: .user(on: .home), - imageProvider: context.imageProvider) + mediaProvider: context.mediaProvider) .accessibilityIdentifier(A11yIdentifiers.homeScreen.userAvatar) .overlayBadge(10, isBadged: context.viewState.requiresExtraAccountSetup) .compositingGroup() diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenInviteCell.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenInviteCell.swift index ed2ee011a..c5dbd87ef 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenInviteCell.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenInviteCell.swift @@ -30,7 +30,7 @@ struct HomeScreenInviteCell: View { if dynamicTypeSize < .accessibility3 { RoomAvatarImage(avatar: room.avatar, avatarSize: .custom(52), - imageProvider: context.imageProvider) + mediaProvider: context.mediaProvider) .dynamicTypeSize(dynamicTypeSize < .accessibility1 ? dynamicTypeSize : .accessibility1) .accessibilityHidden(true) } @@ -79,7 +79,7 @@ struct HomeScreenInviteCell: View { @ViewBuilder private var inviterView: some View { if let inviter = room.inviter, !room.isDirect { - RoomInviterLabel(inviter: inviter, imageProvider: context.imageProvider) + RoomInviterLabel(inviter: inviter, mediaProvider: context.mediaProvider) .font(.compound.bodyMD) .foregroundStyle(.compound.textPlaceholder) } diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift index 17086ffef..9deb47c63 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift @@ -59,7 +59,7 @@ struct HomeScreenRoomCell: View { if dynamicTypeSize < .accessibility3 { RoomAvatarImage(avatar: room.avatar, avatarSize: .room(on: .home), - imageProvider: context.imageProvider) + mediaProvider: context.mediaProvider) .dynamicTypeSize(dynamicTypeSize < .accessibility1 ? dynamicTypeSize : .accessibility1) .accessibilityHidden(true) } diff --git a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift index 55f31122d..a8c89b7ea 100644 --- a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift +++ b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift @@ -42,7 +42,7 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr self.userDiscoveryService = userDiscoveryService self.userIndicatorController = userIndicatorController - super.init(initialViewState: InviteUsersScreenViewState(selectedUsers: selectedUsers.value, isCreatingRoom: roomType.isCreatingRoom), imageProvider: mediaProvider) + super.init(initialViewState: InviteUsersScreenViewState(selectedUsers: selectedUsers.value, isCreatingRoom: roomType.isCreatingRoom), mediaProvider: mediaProvider) setupSubscriptions(selectedUsers: selectedUsers) fetchMembersIfNeeded() diff --git a/ElementX/Sources/Screens/InviteUsersScreen/View/InviteUsersScreen.swift b/ElementX/Sources/Screens/InviteUsersScreen/View/InviteUsersScreen.swift index dc661eaf2..92364d477 100644 --- a/ElementX/Sources/Screens/InviteUsersScreen/View/InviteUsersScreen.swift +++ b/ElementX/Sources/Screens/InviteUsersScreen/View/InviteUsersScreen.swift @@ -88,7 +88,7 @@ struct InviteUsersScreen: View { ForEach(context.viewState.usersSection.users, id: \.userID) { user in UserProfileListRow(user: user, membership: context.viewState.membershipState(user), - imageProvider: context.imageProvider, + mediaProvider: context.mediaProvider, kind: .multiSelection(isSelected: context.viewState.isUserSelected(user)) { context.send(viewAction: .toggleUser(user)) }) @@ -113,7 +113,7 @@ struct InviteUsersScreen: View { ScrollViewReader { scrollView in HStack(spacing: 16) { ForEach(context.viewState.selectedUsers, id: \.userID) { user in - InviteUsersScreenSelectedItem(user: user, imageProvider: context.imageProvider) { + InviteUsersScreenSelectedItem(user: user, mediaProvider: context.mediaProvider) { deselect(user) } .frame(width: cellWidth) diff --git a/ElementX/Sources/Screens/InviteUsersScreen/View/InviteUsersScreenSelectedItem.swift b/ElementX/Sources/Screens/InviteUsersScreen/View/InviteUsersScreenSelectedItem.swift index e08c09b28..015c9737d 100644 --- a/ElementX/Sources/Screens/InviteUsersScreen/View/InviteUsersScreenSelectedItem.swift +++ b/ElementX/Sources/Screens/InviteUsersScreen/View/InviteUsersScreenSelectedItem.swift @@ -18,7 +18,7 @@ import SwiftUI struct InviteUsersScreenSelectedItem: View { let user: UserProfileProxy - let imageProvider: ImageProviderProtocol? + let mediaProvider: MediaProviderProtocol? let dismissAction: () -> Void var body: some View { @@ -39,7 +39,7 @@ struct InviteUsersScreenSelectedItem: View { name: user.displayName, contentID: user.userID, avatarSize: .user(on: .inviteUsers), - imageProvider: imageProvider) + mediaProvider: mediaProvider) .overlay(alignment: .topTrailing) { Button(action: dismissAction) { Image(systemName: "xmark.circle.fill") @@ -59,7 +59,7 @@ struct InviteUsersScreenSelectedItem_Previews: PreviewProvider, TestablePreview ScrollView(.horizontal) { HStack(spacing: 28) { ForEach(people, id: \.userID) { user in - InviteUsersScreenSelectedItem(user: user, imageProvider: MockMediaProvider(), dismissAction: { }) + InviteUsersScreenSelectedItem(user: user, mediaProvider: MockMediaProvider(), dismissAction: { }) .frame(width: 72) } } diff --git a/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenViewModel.swift b/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenViewModel.swift index efe471077..c9ec21916 100644 --- a/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenViewModel.swift @@ -46,7 +46,7 @@ class JoinRoomScreenViewModel: JoinRoomScreenViewModelType, JoinRoomScreenViewMo self.clientProxy = clientProxy self.userIndicatorController = userIndicatorController - super.init(initialViewState: JoinRoomScreenViewState(roomID: roomID), imageProvider: mediaProvider) + super.init(initialViewState: JoinRoomScreenViewState(roomID: roomID), mediaProvider: mediaProvider) Task { await loadRoomDetails() diff --git a/ElementX/Sources/Screens/JoinRoomScreen/View/JoinRoomScreen.swift b/ElementX/Sources/Screens/JoinRoomScreen/View/JoinRoomScreen.swift index dba25600d..41f75438f 100644 --- a/ElementX/Sources/Screens/JoinRoomScreen/View/JoinRoomScreen.swift +++ b/ElementX/Sources/Screens/JoinRoomScreen/View/JoinRoomScreen.swift @@ -42,7 +42,7 @@ struct JoinRoomScreen: View { VStack(spacing: 16) { RoomAvatarImage(avatar: context.viewState.avatar, avatarSize: .room(on: .joinRoom), - imageProvider: context.imageProvider) + mediaProvider: context.mediaProvider) .dynamicTypeSize(dynamicTypeSize < .accessibility1 ? dynamicTypeSize : .accessibility1) VStack(spacing: 8) { @@ -63,7 +63,7 @@ struct JoinRoomScreen: View { } if let inviter = context.viewState.roomDetails?.inviter { - RoomInviterLabel(inviter: inviter, imageProvider: context.imageProvider) + RoomInviterLabel(inviter: inviter, mediaProvider: context.mediaProvider) .font(.compound.bodyMD) .foregroundStyle(.compound.textSecondary) } diff --git a/ElementX/Sources/Screens/MessageForwardingScreen/MessageForwardingScreenViewModel.swift b/ElementX/Sources/Screens/MessageForwardingScreen/MessageForwardingScreenViewModel.swift index 72398da48..799bcef13 100644 --- a/ElementX/Sources/Screens/MessageForwardingScreen/MessageForwardingScreenViewModel.swift +++ b/ElementX/Sources/Screens/MessageForwardingScreen/MessageForwardingScreenViewModel.swift @@ -41,7 +41,7 @@ class MessageForwardingScreenViewModel: MessageForwardingScreenViewModelType, Me self.roomSummaryProvider = roomSummaryProvider self.userIndicatorController = userIndicatorController - super.init(initialViewState: MessageForwardingScreenViewState(), imageProvider: mediaProvider) + super.init(initialViewState: MessageForwardingScreenViewState(), mediaProvider: mediaProvider) roomSummaryProvider.roomListPublisher .receive(on: DispatchQueue.main) diff --git a/ElementX/Sources/Screens/MessageForwardingScreen/View/MessageForwardingScreen.swift b/ElementX/Sources/Screens/MessageForwardingScreen/View/MessageForwardingScreen.swift index b755abed1..86ac42a24 100644 --- a/ElementX/Sources/Screens/MessageForwardingScreen/View/MessageForwardingScreen.swift +++ b/ElementX/Sources/Screens/MessageForwardingScreen/View/MessageForwardingScreen.swift @@ -90,7 +90,7 @@ private struct MessageForwardingListRow: View { if dynamicTypeSize < .accessibility3 { RoomAvatarImage(avatar: room.avatar, avatarSize: .room(on: .messageForwarding), - imageProvider: context.imageProvider) + mediaProvider: context.mediaProvider) .dynamicTypeSize(dynamicTypeSize < .accessibility1 ? dynamicTypeSize : .accessibility1) .accessibilityHidden(true) } diff --git a/ElementX/Sources/Screens/RoomChangeRolesScreen/RoomChangeRolesScreenViewModel.swift b/ElementX/Sources/Screens/RoomChangeRolesScreen/RoomChangeRolesScreenViewModel.swift index c86303f4d..016c235c1 100644 --- a/ElementX/Sources/Screens/RoomChangeRolesScreen/RoomChangeRolesScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomChangeRolesScreen/RoomChangeRolesScreenViewModel.swift @@ -45,7 +45,7 @@ class RoomChangeRolesScreenViewModel: RoomChangeRolesScreenViewModelType, RoomCh moderators: [], users: [], bindings: .init()), - imageProvider: mediaProvider) + mediaProvider: mediaProvider) roomProxy.membersPublisher .receive(on: DispatchQueue.main) diff --git a/ElementX/Sources/Screens/RoomChangeRolesScreen/View/RoomChangeRolesScreen.swift b/ElementX/Sources/Screens/RoomChangeRolesScreen/View/RoomChangeRolesScreen.swift index 734868eae..11bee0f65 100644 --- a/ElementX/Sources/Screens/RoomChangeRolesScreen/View/RoomChangeRolesScreen.swift +++ b/ElementX/Sources/Screens/RoomChangeRolesScreen/View/RoomChangeRolesScreen.swift @@ -74,7 +74,7 @@ struct RoomChangeRolesScreen: View { ScrollViewReader { scrollView in HStack(spacing: 16) { ForEach(context.viewState.membersWithRole, id: \.id) { member in - RoomChangeRolesScreenSelectedItem(member: member, imageProvider: context.imageProvider) { + RoomChangeRolesScreenSelectedItem(member: member, mediaProvider: context.mediaProvider) { context.send(viewAction: .demoteMember(member)) } .frame(width: cellWidth) diff --git a/ElementX/Sources/Screens/RoomChangeRolesScreen/View/RoomChangeRolesScreenRow.swift b/ElementX/Sources/Screens/RoomChangeRolesScreen/View/RoomChangeRolesScreenRow.swift index 6048a22cb..2bd544f35 100644 --- a/ElementX/Sources/Screens/RoomChangeRolesScreen/View/RoomChangeRolesScreenRow.swift +++ b/ElementX/Sources/Screens/RoomChangeRolesScreen/View/RoomChangeRolesScreenRow.swift @@ -20,7 +20,7 @@ import SwiftUI struct RoomChangeRolesScreenRow: View { let member: RoomMemberDetails - let imageProvider: ImageProviderProtocol? + let mediaProvider: MediaProviderProtocol? let isSelected: Bool let action: () -> Void @@ -38,7 +38,7 @@ struct RoomChangeRolesScreenRow: View { name: member.name, contentID: member.id, avatarSize: .user(on: .startChat), - imageProvider: imageProvider) + mediaProvider: mediaProvider) } } @@ -48,34 +48,34 @@ struct RoomChangeRolesScreenRow_Previews: PreviewProvider, TestablePreview { static var previews: some View { Form { RoomChangeRolesScreenRow(member: .init(withProxy: RoomMemberProxyMock.mockAlice), - imageProvider: MockMediaProvider(), + mediaProvider: MockMediaProvider(), isSelected: true, action: action) RoomChangeRolesScreenRow(member: .init(withProxy: RoomMemberProxyMock.mockBob), - imageProvider: MockMediaProvider(), + mediaProvider: MockMediaProvider(), isSelected: false, action: action) RoomChangeRolesScreenRow(member: .init(withProxy: RoomMemberProxyMock.mockInvited), - imageProvider: MockMediaProvider(), + mediaProvider: MockMediaProvider(), isSelected: false, action: action) RoomChangeRolesScreenRow(member: .init(withProxy: RoomMemberProxyMock.mockCharlie), - imageProvider: MockMediaProvider(), + mediaProvider: MockMediaProvider(), isSelected: true, action: action) .disabled(true) RoomChangeRolesScreenRow(member: .init(withProxy: RoomMemberProxyMock(with: .init(userID: "@someone:matrix.org", membership: .join))), - imageProvider: MockMediaProvider(), + mediaProvider: MockMediaProvider(), isSelected: false, action: action) .disabled(true) RoomChangeRolesScreenRow(member: .init(withProxy: RoomMemberProxyMock(with: .init(userID: "@someone:matrix.org", membership: .join))), - imageProvider: MockMediaProvider(), + mediaProvider: MockMediaProvider(), isSelected: false, action: action) } diff --git a/ElementX/Sources/Screens/RoomChangeRolesScreen/View/RoomChangeRolesScreenSection.swift b/ElementX/Sources/Screens/RoomChangeRolesScreen/View/RoomChangeRolesScreenSection.swift index b2a06ffee..913be9bd2 100644 --- a/ElementX/Sources/Screens/RoomChangeRolesScreen/View/RoomChangeRolesScreenSection.swift +++ b/ElementX/Sources/Screens/RoomChangeRolesScreen/View/RoomChangeRolesScreenSection.swift @@ -29,7 +29,7 @@ struct RoomChangeRolesScreenSection: View { Section { ForEach(members, id: \.id) { member in RoomChangeRolesScreenRow(member: member, - imageProvider: context.imageProvider, + mediaProvider: context.mediaProvider, isSelected: isMemberSelected(member)) { context.send(viewAction: .toggleMember(member)) } diff --git a/ElementX/Sources/Screens/RoomChangeRolesScreen/View/RoomChangeRolesScreenSelectedItem.swift b/ElementX/Sources/Screens/RoomChangeRolesScreen/View/RoomChangeRolesScreenSelectedItem.swift index 462590787..be8834932 100644 --- a/ElementX/Sources/Screens/RoomChangeRolesScreen/View/RoomChangeRolesScreenSelectedItem.swift +++ b/ElementX/Sources/Screens/RoomChangeRolesScreen/View/RoomChangeRolesScreenSelectedItem.swift @@ -18,7 +18,7 @@ import SwiftUI struct RoomChangeRolesScreenSelectedItem: View { let member: RoomMemberDetails - let imageProvider: ImageProviderProtocol? + let mediaProvider: MediaProviderProtocol? let dismissAction: () -> Void var body: some View { @@ -39,7 +39,7 @@ struct RoomChangeRolesScreenSelectedItem: View { name: member.name, contentID: member.id, avatarSize: .user(on: .inviteUsers), - imageProvider: imageProvider) + mediaProvider: mediaProvider) .overlay(alignment: .topTrailing) { if member.role != .administrator { Button(action: dismissAction) { @@ -69,7 +69,7 @@ struct RoomChangeRolesScreenSelectedItem_Previews: PreviewProvider, TestablePrev HStack(spacing: 12) { ForEach(members, id: \.id) { member in RoomChangeRolesScreenSelectedItem(member: member, - imageProvider: MockMediaProvider(), + mediaProvider: MockMediaProvider(), dismissAction: { }) .frame(width: 72) } diff --git a/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenViewModel.swift b/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenViewModel.swift index abbef093c..7e65f13bb 100644 --- a/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenViewModel.swift @@ -44,7 +44,7 @@ class RoomDetailsEditScreenViewModel: RoomDetailsEditScreenViewModelType, RoomDe initialName: roomName ?? "", initialTopic: roomTopic ?? "", avatarURL: roomAvatar, - bindings: .init(name: roomName ?? "", topic: roomTopic ?? "")), imageProvider: mediaProvider) + bindings: .init(name: roomName ?? "", topic: roomTopic ?? "")), mediaProvider: mediaProvider) Task { // Can't use async let because the mocks aren't thread safe when calling the same method 🤦‍♂️ diff --git a/ElementX/Sources/Screens/RoomDetailsEditScreen/View/RoomDetailsEditScreen.swift b/ElementX/Sources/Screens/RoomDetailsEditScreen/View/RoomDetailsEditScreen.swift index 4266bb808..67e14224c 100644 --- a/ElementX/Sources/Screens/RoomDetailsEditScreen/View/RoomDetailsEditScreen.swift +++ b/ElementX/Sources/Screens/RoomDetailsEditScreen/View/RoomDetailsEditScreen.swift @@ -68,7 +68,7 @@ struct RoomDetailsEditScreen: View { name: context.viewState.initialName, contentID: context.viewState.roomID, avatarSize: .user(on: .memberDetails), - imageProvider: context.imageProvider) + mediaProvider: context.mediaProvider) .overlay(alignment: .bottomTrailing) { if context.viewState.canEditAvatar { avatarOverlayIcon diff --git a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift index dc40f316a..edb9fb96e 100644 --- a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift @@ -82,7 +82,7 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr joinedMembersCount: roomProxy.joinedMembersCount, notificationSettingsState: .loading, bindings: .init()), - imageProvider: mediaProvider) + mediaProvider: mediaProvider) appSettings.$pinningEnabled .weakAssign(to: \.state.isPinningEnabled, on: self) diff --git a/ElementX/Sources/Screens/RoomDetailsScreen/View/RoomDetailsScreen.swift b/ElementX/Sources/Screens/RoomDetailsScreen/View/RoomDetailsScreen.swift index bae61abee..838a6a504 100644 --- a/ElementX/Sources/Screens/RoomDetailsScreen/View/RoomDetailsScreen.swift +++ b/ElementX/Sources/Screens/RoomDetailsScreen/View/RoomDetailsScreen.swift @@ -74,7 +74,7 @@ struct RoomDetailsScreen: View { private var normalRoomHeaderSection: some View { AvatarHeaderView(room: context.viewState.details, avatarSize: .room(on: .details), - imageProvider: context.imageProvider) { + mediaProvider: context.mediaProvider) { context.send(viewAction: .displayAvatar) } footer: { if !context.viewState.shortcuts.isEmpty { @@ -87,7 +87,7 @@ struct RoomDetailsScreen: View { private func dmHeaderSection(accountOwner: RoomMemberDetails, recipient: RoomMemberDetails) -> some View { AvatarHeaderView(accountOwner: accountOwner, dmRecipient: recipient, - imageProvider: context.imageProvider) { + mediaProvider: context.mediaProvider) { context.send(viewAction: .displayAvatar) } footer: { if !context.viewState.shortcuts.isEmpty { diff --git a/ElementX/Sources/Screens/RoomDirectorySearchScreen/RoomDirectorySearchScreenCoordinator.swift b/ElementX/Sources/Screens/RoomDirectorySearchScreen/RoomDirectorySearchScreenCoordinator.swift index 3c56efe33..8e88e40e2 100644 --- a/ElementX/Sources/Screens/RoomDirectorySearchScreen/RoomDirectorySearchScreenCoordinator.swift +++ b/ElementX/Sources/Screens/RoomDirectorySearchScreen/RoomDirectorySearchScreenCoordinator.swift @@ -19,7 +19,7 @@ import SwiftUI struct RoomDirectorySearchScreenCoordinatorParameters { let clientProxy: ClientProxyProtocol - let imageProvider: ImageProviderProtocol + let mediaProvider: MediaProviderProtocol let userIndicatorController: UserIndicatorControllerProtocol } @@ -42,7 +42,7 @@ final class RoomDirectorySearchScreenCoordinator: CoordinatorProtocol { init(parameters: RoomDirectorySearchScreenCoordinatorParameters) { viewModel = RoomDirectorySearchScreenViewModel(clientProxy: parameters.clientProxy, userIndicatorController: parameters.userIndicatorController, - imageProvider: parameters.imageProvider) + mediaProvider: parameters.mediaProvider) } func start() { diff --git a/ElementX/Sources/Screens/RoomDirectorySearchScreen/RoomDirectorySearchScreenViewModel.swift b/ElementX/Sources/Screens/RoomDirectorySearchScreen/RoomDirectorySearchScreenViewModel.swift index 1e5ebb557..521b76a57 100644 --- a/ElementX/Sources/Screens/RoomDirectorySearchScreen/RoomDirectorySearchScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomDirectorySearchScreen/RoomDirectorySearchScreenViewModel.swift @@ -30,11 +30,11 @@ class RoomDirectorySearchScreenViewModel: RoomDirectorySearchScreenViewModelType init(clientProxy: ClientProxyProtocol, userIndicatorController: UserIndicatorControllerProtocol, - imageProvider: ImageProviderProtocol) { + mediaProvider: MediaProviderProtocol) { roomDirectorySearchProxy = clientProxy.roomDirectorySearchProxy() self.userIndicatorController = userIndicatorController - super.init(initialViewState: RoomDirectorySearchScreenViewState(), imageProvider: imageProvider) + super.init(initialViewState: RoomDirectorySearchScreenViewState(), mediaProvider: mediaProvider) state.rooms = roomDirectorySearchProxy.resultsPublisher.value diff --git a/ElementX/Sources/Screens/RoomDirectorySearchScreen/View/RoomDirectoryCell.swift b/ElementX/Sources/Screens/RoomDirectorySearchScreen/View/RoomDirectoryCell.swift index 53ca9e5b4..b85e822f2 100644 --- a/ElementX/Sources/Screens/RoomDirectorySearchScreen/View/RoomDirectoryCell.swift +++ b/ElementX/Sources/Screens/RoomDirectorySearchScreen/View/RoomDirectoryCell.swift @@ -19,7 +19,7 @@ import SwiftUI struct RoomDirectorySearchCell: View { let result: RoomDirectorySearchResult - let imageProvider: ImageProviderProtocol? + let mediaProvider: MediaProviderProtocol? let joinAction: () -> Void private var description: String? { @@ -46,7 +46,7 @@ struct RoomDirectorySearchCell: View { private var avatar: some View { RoomAvatarImage(avatar: result.avatar, avatarSize: .room(on: .roomDirectorySearch), - imageProvider: imageProvider) + mediaProvider: mediaProvider) .accessibilityHidden(true) } } @@ -64,7 +64,7 @@ struct RoomDirectorySearchCell_Previews: PreviewProvider, TestablePreview { name: "Test title", avatarURL: nil), canBeJoined: true), - imageProvider: MockMediaProvider()) { } + mediaProvider: MockMediaProvider()) { } RoomDirectorySearchCell(result: .init(id: "!test_id_2:matrix.org", alias: "#test:example.com", @@ -74,7 +74,7 @@ struct RoomDirectorySearchCell_Previews: PreviewProvider, TestablePreview { name: nil, avatarURL: nil), canBeJoined: true), - imageProvider: MockMediaProvider()) { } + mediaProvider: MockMediaProvider()) { } RoomDirectorySearchCell(result: .init(id: "!test_id_3:example.com", alias: "#test_no_topic:example.com", @@ -84,7 +84,7 @@ struct RoomDirectorySearchCell_Previews: PreviewProvider, TestablePreview { name: "Test title no topic", avatarURL: nil), canBeJoined: true), - imageProvider: MockMediaProvider()) { } + mediaProvider: MockMediaProvider()) { } RoomDirectorySearchCell(result: .init(id: "!test_id_4:example.com", alias: "#test_no_topic:example.com", @@ -94,7 +94,7 @@ struct RoomDirectorySearchCell_Previews: PreviewProvider, TestablePreview { name: nil, avatarURL: nil), canBeJoined: true), - imageProvider: MockMediaProvider()) { } + mediaProvider: MockMediaProvider()) { } RoomDirectorySearchCell(result: .init(id: "!test_id_5:example.com", alias: nil, @@ -104,7 +104,7 @@ struct RoomDirectorySearchCell_Previews: PreviewProvider, TestablePreview { name: "Test title no alias", avatarURL: nil), canBeJoined: false), - imageProvider: MockMediaProvider()) { } + mediaProvider: MockMediaProvider()) { } RoomDirectorySearchCell(result: .init(id: "!test_id_6:example.com", alias: nil, @@ -114,7 +114,7 @@ struct RoomDirectorySearchCell_Previews: PreviewProvider, TestablePreview { name: "Test title no alias", avatarURL: nil), canBeJoined: false), - imageProvider: MockMediaProvider()) { } + mediaProvider: MockMediaProvider()) { } RoomDirectorySearchCell(result: .init(id: "!test_id_7:example.com", alias: nil, @@ -124,7 +124,7 @@ struct RoomDirectorySearchCell_Previews: PreviewProvider, TestablePreview { name: nil, avatarURL: nil), canBeJoined: false), - imageProvider: MockMediaProvider()) { } + mediaProvider: MockMediaProvider()) { } RoomDirectorySearchCell(result: .init(id: "!test_id_8:example.com", alias: nil, name: nil, @@ -133,7 +133,7 @@ struct RoomDirectorySearchCell_Previews: PreviewProvider, TestablePreview { name: nil, avatarURL: nil), canBeJoined: false), - imageProvider: MockMediaProvider()) { } + mediaProvider: MockMediaProvider()) { } } .compoundList() } diff --git a/ElementX/Sources/Screens/RoomDirectorySearchScreen/View/RoomDirectorySearchScreen.swift b/ElementX/Sources/Screens/RoomDirectorySearchScreen/View/RoomDirectorySearchScreen.swift index 19b8b395d..01d036c5f 100644 --- a/ElementX/Sources/Screens/RoomDirectorySearchScreen/View/RoomDirectorySearchScreen.swift +++ b/ElementX/Sources/Screens/RoomDirectorySearchScreen/View/RoomDirectorySearchScreen.swift @@ -25,7 +25,7 @@ struct RoomDirectorySearchScreen: View { List { Section { ForEach(context.viewState.rooms) { room in - RoomDirectorySearchCell(result: room, imageProvider: context.imageProvider) { + RoomDirectorySearchCell(result: room, mediaProvider: context.mediaProvider) { context.send(viewAction: .select(room: room)) } } @@ -102,7 +102,7 @@ struct RoomDirectorySearchScreen_Previews: PreviewProvider, TestablePreview { return RoomDirectorySearchScreenViewModel(clientProxy: clientProxy, userIndicatorController: UserIndicatorControllerMock(), - imageProvider: MockMediaProvider()) + mediaProvider: MockMediaProvider()) }() static var previews: some View { diff --git a/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenViewModel.swift b/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenViewModel.swift index 0592098fa..07a2a6441 100644 --- a/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenViewModel.swift @@ -48,7 +48,7 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro let initialViewState = RoomMemberDetailsScreenViewState(userID: userID, bindings: .init()) - super.init(initialViewState: initialViewState, imageProvider: mediaProvider) + super.init(initialViewState: initialViewState, mediaProvider: mediaProvider) showMemberLoadingIndicator() Task { diff --git a/ElementX/Sources/Screens/RoomMemberDetailsScreen/View/RoomMemberDetailsScreen.swift b/ElementX/Sources/Screens/RoomMemberDetailsScreen/View/RoomMemberDetailsScreen.swift index b9dae5200..e69e29a87 100644 --- a/ElementX/Sources/Screens/RoomMemberDetailsScreen/View/RoomMemberDetailsScreen.swift +++ b/ElementX/Sources/Screens/RoomMemberDetailsScreen/View/RoomMemberDetailsScreen.swift @@ -75,7 +75,7 @@ struct RoomMemberDetailsScreen: View { if let memberDetails = context.viewState.memberDetails { AvatarHeaderView(member: memberDetails, avatarSize: .user(on: .memberDetails), - imageProvider: context.imageProvider) { + mediaProvider: context.mediaProvider) { context.send(viewAction: .displayAvatar) } footer: { otherUserFooter @@ -83,7 +83,7 @@ struct RoomMemberDetailsScreen: View { } else { AvatarHeaderView(user: UserProfileProxy(userID: context.viewState.userID), avatarSize: .user(on: .memberDetails), - imageProvider: context.imageProvider, + mediaProvider: context.mediaProvider, footer: { }) } } diff --git a/ElementX/Sources/Screens/RoomMemberListScreen/RoomMembersListScreenViewModel.swift b/ElementX/Sources/Screens/RoomMemberListScreen/RoomMembersListScreenViewModel.swift index 605147af2..ab5f3eb2a 100644 --- a/ElementX/Sources/Screens/RoomMemberListScreen/RoomMembersListScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomMemberListScreen/RoomMembersListScreenViewModel.swift @@ -43,7 +43,7 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe super.init(initialViewState: .init(joinedMembersCount: roomProxy.joinedMembersCount, bindings: .init(mode: initialMode)), - imageProvider: mediaProvider) + mediaProvider: mediaProvider) setupMembers() } diff --git a/ElementX/Sources/Screens/RoomMemberListScreen/View/RoomMembersListManageMemberSheet.swift b/ElementX/Sources/Screens/RoomMemberListScreen/View/RoomMembersListManageMemberSheet.swift index f516409f2..f1bd9b04d 100644 --- a/ElementX/Sources/Screens/RoomMemberListScreen/View/RoomMembersListManageMemberSheet.swift +++ b/ElementX/Sources/Screens/RoomMemberListScreen/View/RoomMembersListManageMemberSheet.swift @@ -29,7 +29,7 @@ struct RoomMembersListManageMemberSheet: View { Form { AvatarHeaderView(member: member, avatarSize: .user(on: .memberDetails), - imageProvider: context.imageProvider) { + mediaProvider: context.mediaProvider) { EmptyView() } diff --git a/ElementX/Sources/Screens/RoomMemberListScreen/View/RoomMembersListScreenMemberCell.swift b/ElementX/Sources/Screens/RoomMemberListScreen/View/RoomMembersListScreenMemberCell.swift index 2a6d3dbc1..98add929c 100644 --- a/ElementX/Sources/Screens/RoomMemberListScreen/View/RoomMembersListScreenMemberCell.swift +++ b/ElementX/Sources/Screens/RoomMemberListScreen/View/RoomMembersListScreenMemberCell.swift @@ -29,7 +29,7 @@ struct RoomMembersListScreenMemberCell: View { name: avatarName, contentID: member.id, avatarSize: .user(on: .roomDetails), - imageProvider: context.imageProvider) + mediaProvider: context.mediaProvider) .accessibilityHidden(true) HStack(alignment: .firstTextBaseline, spacing: 4) { diff --git a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/ComposerToolbarViewModel.swift b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/ComposerToolbarViewModel.swift index 44a38f896..7e9b9c87a 100644 --- a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/ComposerToolbarViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/ComposerToolbarViewModel.swift @@ -67,7 +67,7 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool super.init(initialViewState: ComposerToolbarViewState(audioPlayerState: .init(id: .recorderPreview, duration: 0), audioRecorderState: .init(), bindings: .init()), - imageProvider: mediaProvider) + mediaProvider: mediaProvider) context.$viewState .map(\.composerMode) diff --git a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/View/CompletionSuggestionView.swift b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/View/CompletionSuggestionView.swift index dcc98b7e6..6e4627b73 100644 --- a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/View/CompletionSuggestionView.swift +++ b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/View/CompletionSuggestionView.swift @@ -17,7 +17,7 @@ import SwiftUI struct CompletionSuggestionView: View { - let imageProvider: ImageProviderProtocol? + let mediaProvider: MediaProviderProtocol? let items: [SuggestionItem] var showBackgroundShadow = true let onTap: (SuggestionItem) -> Void @@ -41,7 +41,7 @@ struct CompletionSuggestionView: View { EmptyView() } else { ZStack { - MentionSuggestionItemView(imageProvider: nil, item: .init(id: "", displayName: nil, avatarURL: nil, range: .init())) + MentionSuggestionItemView(mediaProvider: nil, item: .init(id: "", displayName: nil, avatarURL: nil, range: .init())) .readFrame($prototypeListItemFrame) .hidden() if showBackgroundShadow { @@ -63,7 +63,7 @@ struct CompletionSuggestionView: View { } label: { switch item { case .user(let mention), .allUsers(let mention): - MentionSuggestionItemView(imageProvider: imageProvider, item: mention) + MentionSuggestionItemView(mediaProvider: mediaProvider, item: mention) } } .modifier(ListItemPaddingModifier(isFirst: items.first?.id == item.id)) @@ -125,12 +125,12 @@ struct CompletionSuggestion_Previews: PreviewProvider, TestablePreview { static var previews: some View { // Putting them is VStack allows the preview to work properly in tests VStack(spacing: 8) { - CompletionSuggestionView(imageProvider: MockMediaProvider(), + CompletionSuggestionView(mediaProvider: MockMediaProvider(), items: [.user(item: MentionSuggestionItem(id: "@user_mention_1:matrix.org", displayName: "User 1", avatarURL: nil, range: .init())), .user(item: MentionSuggestionItem(id: "@user_mention_2:matrix.org", displayName: "User 2", avatarURL: URL.documentsDirectory, range: .init()))]) { _ in } } VStack(spacing: 8) { - CompletionSuggestionView(imageProvider: MockMediaProvider(), + CompletionSuggestionView(mediaProvider: MockMediaProvider(), items: multipleItems) { _ in } } } diff --git a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/View/ComposerToolbar.swift b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/View/ComposerToolbar.swift index d6942e73c..d10b6bb8a 100644 --- a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/View/ComposerToolbar.swift +++ b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/View/ComposerToolbar.swift @@ -57,7 +57,7 @@ struct ComposerToolbar: View { } private var suggestionView: some View { - CompletionSuggestionView(imageProvider: context.imageProvider, + CompletionSuggestionView(mediaProvider: context.mediaProvider, items: context.viewState.suggestions, showBackgroundShadow: !context.composerExpanded) { suggestion in context.send(viewAction: .selectedSuggestion(suggestion)) diff --git a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/View/MentionSuggestionItemView.swift b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/View/MentionSuggestionItemView.swift index 19a61bcba..3740a67a2 100644 --- a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/View/MentionSuggestionItemView.swift +++ b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/View/MentionSuggestionItemView.swift @@ -17,7 +17,7 @@ import SwiftUI struct MentionSuggestionItemView: View { - let imageProvider: ImageProviderProtocol? + let mediaProvider: MediaProviderProtocol? let item: MentionSuggestionItem var body: some View { @@ -26,7 +26,7 @@ struct MentionSuggestionItemView: View { name: item.displayName, contentID: item.id, avatarSize: .user(on: .suggestions), - imageProvider: imageProvider) + mediaProvider: mediaProvider) VStack(alignment: .leading, spacing: 0) { Text(item.displayName ?? item.id) .font(.compound.bodyLG) @@ -47,7 +47,7 @@ struct MentionSuggestionItemView_Previews: PreviewProvider, TestablePreview { static let mockMediaProvider = MockMediaProvider() static var previews: some View { - MentionSuggestionItemView(imageProvider: mockMediaProvider, item: .init(id: "test", displayName: "Test", avatarURL: URL.documentsDirectory, range: .init())) - MentionSuggestionItemView(imageProvider: mockMediaProvider, item: .init(id: "test2", displayName: nil, avatarURL: nil, range: .init())) + MentionSuggestionItemView(mediaProvider: mockMediaProvider, item: .init(id: "test", displayName: "Test", avatarURL: URL.documentsDirectory, range: .init())) + MentionSuggestionItemView(mediaProvider: mockMediaProvider, item: .init(id: "test2", displayName: nil, avatarURL: nil, range: .init())) } } diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index 62d5b63a2..41106e221 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -66,7 +66,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol roomAvatar: roomProxy.avatar, hasOngoingCall: roomProxy.hasOngoingCall, bindings: .init()), - imageProvider: mediaProvider) + mediaProvider: mediaProvider) setupSubscriptions() } diff --git a/ElementX/Sources/Screens/RoomScreen/View/RoomHeaderView.swift b/ElementX/Sources/Screens/RoomScreen/View/RoomHeaderView.swift index e0cc306f4..1827d2cbf 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/RoomHeaderView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/RoomHeaderView.swift @@ -22,7 +22,7 @@ struct RoomHeaderView: View { let roomName: String let roomAvatar: RoomAvatar - let imageProvider: ImageProviderProtocol? + let mediaProvider: MediaProviderProtocol? var body: some View { HStack(spacing: 12) { @@ -39,7 +39,7 @@ struct RoomHeaderView: View { private var avatarImage: some View { RoomAvatarImage(avatar: roomAvatar, avatarSize: .room(on: .timeline), - imageProvider: imageProvider) + mediaProvider: mediaProvider) .accessibilityIdentifier(A11yIdentifiers.roomScreen.avatar) } } @@ -50,7 +50,7 @@ struct RoomHeaderView_Previews: PreviewProvider, TestablePreview { roomAvatar: .room(id: "1", name: "Some Room Name", avatarURL: URL.picturesDirectory), - imageProvider: MockMediaProvider()) + mediaProvider: MockMediaProvider()) .previewLayout(.sizeThatFits) .padding() @@ -58,7 +58,7 @@ struct RoomHeaderView_Previews: PreviewProvider, TestablePreview { roomAvatar: .room(id: "1", name: "Some Room Name", avatarURL: nil), - imageProvider: MockMediaProvider()) + mediaProvider: MockMediaProvider()) .previewLayout(.sizeThatFits) .padding() } diff --git a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift index 3550e8023..21ba52901 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift @@ -82,7 +82,7 @@ struct RoomScreen: View { } } .sheet(item: $timelineContext.reactionSummaryInfo) { - ReactionsSummaryView(reactions: $0.reactions, members: timelineContext.viewState.members, imageProvider: timelineContext.imageProvider, selectedReactionKey: $0.selectedKey) + ReactionsSummaryView(reactions: $0.reactions, members: timelineContext.viewState.members, mediaProvider: timelineContext.mediaProvider, selectedReactionKey: $0.selectedKey) .edgesIgnoringSafeArea([.bottom]) } .sheet(item: $timelineContext.readReceiptsSummaryInfo) { @@ -165,7 +165,7 @@ struct RoomScreen: View { ToolbarItem(placement: .principal) { RoomHeaderView(roomName: roomContext.viewState.roomTitle, roomAvatar: roomContext.viewState.roomAvatar, - imageProvider: roomContext.imageProvider) + mediaProvider: roomContext.mediaProvider) // Using a button stops it from getting truncated in the navigation bar .contentShape(.rect) .onTapGesture { diff --git a/ElementX/Sources/Screens/Settings/NotificationSettingsEditScreen/NotificationSettingsEditScreenViewModel.swift b/ElementX/Sources/Screens/Settings/NotificationSettingsEditScreen/NotificationSettingsEditScreenViewModel.swift index defb98dcd..58af7aa49 100644 --- a/ElementX/Sources/Screens/Settings/NotificationSettingsEditScreen/NotificationSettingsEditScreenViewModel.swift +++ b/ElementX/Sources/Screens/Settings/NotificationSettingsEditScreen/NotificationSettingsEditScreenViewModel.swift @@ -44,7 +44,7 @@ class NotificationSettingsEditScreenViewModel: NotificationSettingsEditScreenVie super.init(initialViewState: NotificationSettingsEditScreenViewState(bindings: bindings, strings: NotificationSettingsEditScreenStrings(chatType: chatType)), - imageProvider: userSession.mediaProvider) + mediaProvider: userSession.mediaProvider) setupNotificationSettingsSubscription() setupRoomSummaryProviderSubscription() diff --git a/ElementX/Sources/Screens/Settings/NotificationSettingsEditScreen/View/NotificationSettingsEditScreenRoomCell.swift b/ElementX/Sources/Screens/Settings/NotificationSettingsEditScreen/View/NotificationSettingsEditScreenRoomCell.swift index 0db0f696b..ce2fd9ffa 100644 --- a/ElementX/Sources/Screens/Settings/NotificationSettingsEditScreen/View/NotificationSettingsEditScreenRoomCell.swift +++ b/ElementX/Sources/Screens/Settings/NotificationSettingsEditScreen/View/NotificationSettingsEditScreenRoomCell.swift @@ -41,7 +41,7 @@ struct NotificationSettingsEditScreenRoomCell: View { if dynamicTypeSize < .accessibility3 { RoomAvatarImage(avatar: room.avatar, avatarSize: .room(on: .notificationSettings), - imageProvider: context.imageProvider) + mediaProvider: context.mediaProvider) .dynamicTypeSize(dynamicTypeSize < .accessibility1 ? dynamicTypeSize : .accessibility1) .accessibilityHidden(true) } diff --git a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift index d8dfd5ba6..ccab28bd4 100644 --- a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift +++ b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift @@ -30,7 +30,7 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo super.init(initialViewState: .init(deviceID: userSession.clientProxy.deviceID, userID: userSession.clientProxy.userID, showDeveloperOptions: AppSettings.isDevelopmentBuild), - imageProvider: userSession.mediaProvider) + mediaProvider: userSession.mediaProvider) userSession.clientProxy.userAvatarURLPublisher .receive(on: DispatchQueue.main) diff --git a/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift b/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift index fa38cdbde..f911478c1 100644 --- a/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift +++ b/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift @@ -56,7 +56,7 @@ struct SettingsScreen: View { name: context.viewState.userDisplayName, contentID: context.viewState.userID, avatarSize: .user(on: .settings), - imageProvider: context.imageProvider) + mediaProvider: context.mediaProvider) .accessibilityHidden(true) VStack(alignment: .leading, spacing: 2) { diff --git a/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/UserDetailsEditScreenViewModel.swift b/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/UserDetailsEditScreenViewModel.swift index d9ea18e34..23d64898b 100644 --- a/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/UserDetailsEditScreenViewModel.swift +++ b/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/UserDetailsEditScreenViewModel.swift @@ -36,7 +36,7 @@ class UserDetailsEditScreenViewModel: UserDetailsEditScreenViewModelType, UserDe self.userIndicatorController = userIndicatorController super.init(initialViewState: UserDetailsEditScreenViewState(userID: clientProxy.userID, - bindings: .init()), imageProvider: mediaProvider) + bindings: .init()), mediaProvider: mediaProvider) clientProxy.userAvatarURLPublisher .receive(on: DispatchQueue.main) diff --git a/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/View/UserDetailsEditScreen.swift b/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/View/UserDetailsEditScreen.swift index f3d57b6ed..072f36eeb 100644 --- a/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/View/UserDetailsEditScreen.swift +++ b/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/View/UserDetailsEditScreen.swift @@ -64,7 +64,7 @@ struct UserDetailsEditScreen: View { name: context.viewState.currentDisplayName, contentID: context.viewState.userID, avatarSize: .user(on: .editUserDetails), - imageProvider: context.imageProvider) + mediaProvider: context.mediaProvider) .overlay(alignment: .bottomTrailing) { avatarOverlayIcon } diff --git a/ElementX/Sources/Screens/StartChatScreen/StartChatScreenViewModel.swift b/ElementX/Sources/Screens/StartChatScreen/StartChatScreenViewModel.swift index a609c0125..9baf3fcb0 100644 --- a/ElementX/Sources/Screens/StartChatScreen/StartChatScreenViewModel.swift +++ b/ElementX/Sources/Screens/StartChatScreen/StartChatScreenViewModel.swift @@ -41,7 +41,7 @@ class StartChatScreenViewModel: StartChatScreenViewModelType, StartChatScreenVie self.userIndicatorController = userIndicatorController self.userDiscoveryService = userDiscoveryService - super.init(initialViewState: StartChatScreenViewState(userID: userSession.clientProxy.userID), imageProvider: userSession.mediaProvider) + super.init(initialViewState: StartChatScreenViewState(userID: userSession.clientProxy.userID), mediaProvider: userSession.mediaProvider) setupBindings() diff --git a/ElementX/Sources/Screens/StartChatScreen/View/StartChatScreen.swift b/ElementX/Sources/Screens/StartChatScreen/View/StartChatScreen.swift index 735817d09..b69a90244 100644 --- a/ElementX/Sources/Screens/StartChatScreen/View/StartChatScreen.swift +++ b/ElementX/Sources/Screens/StartChatScreen/View/StartChatScreen.swift @@ -90,7 +90,7 @@ struct StartChatScreen: View { ForEach(context.viewState.usersSection.users, id: \.userID) { user in UserProfileListRow(user: user, membership: nil, - imageProvider: context.imageProvider, + mediaProvider: context.mediaProvider, kind: .button { context.send(viewAction: .selectUser(user)) }) diff --git a/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift b/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift index adba82c0f..f024735e6 100644 --- a/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift +++ b/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift @@ -86,7 +86,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol { ownUserID: roomProxy.ownUserID, isViewSourceEnabled: appSettings.viewSourceEnabled, bindings: .init(reactionsCollapsed: [:])), - imageProvider: mediaProvider) + mediaProvider: mediaProvider) if focussedEventID != nil { // The timeline controller will start loading a detached timeline. diff --git a/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenu.swift b/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenu.swift index e1adbf978..c31eb4d59 100644 --- a/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenu.swift +++ b/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenu.swift @@ -70,7 +70,7 @@ struct TimelineItemMenu: View { name: item.sender.displayName, contentID: item.sender.id, avatarSize: .user(on: .timeline), - imageProvider: context.imageProvider) + mediaProvider: context.mediaProvider) .accessibilityHidden(true) Spacer(minLength: 8.0) diff --git a/ElementX/Sources/Screens/Timeline/View/ReadReceipts/ReadReceiptCell.swift b/ElementX/Sources/Screens/Timeline/View/ReadReceipts/ReadReceiptCell.swift index a920bf522..d3a1e896a 100644 --- a/ElementX/Sources/Screens/Timeline/View/ReadReceipts/ReadReceiptCell.swift +++ b/ElementX/Sources/Screens/Timeline/View/ReadReceipts/ReadReceiptCell.swift @@ -19,7 +19,7 @@ import SwiftUI struct ReadReceiptCell: View { let readReceipt: ReadReceipt let memberState: RoomMemberState? - let imageProvider: ImageProviderProtocol? + let mediaProvider: MediaProviderProtocol? private var title: String { memberState?.displayName ?? readReceipt.userID @@ -38,7 +38,7 @@ struct ReadReceiptCell: View { name: memberState?.displayName, contentID: readReceipt.userID, avatarSize: .user(on: .readReceiptSheet), - imageProvider: imageProvider) + mediaProvider: mediaProvider) VStack(alignment: .leading, spacing: 0) { HStack(spacing: 12) { Text(title) @@ -70,18 +70,18 @@ struct ReadReceiptCell_Previews: PreviewProvider, TestablePreview { formattedTimestamp: "10:00"), memberState: .init(displayName: "Test", avatarURL: nil), - imageProvider: MockMediaProvider()) + mediaProvider: MockMediaProvider()) .previewDisplayName("No Image") ReadReceiptCell(readReceipt: .init(userID: "@test:matrix.org", formattedTimestamp: "10:00"), memberState: .init(displayName: "Test", avatarURL: URL.documentsDirectory), - imageProvider: MockMediaProvider()) + mediaProvider: MockMediaProvider()) .previewDisplayName("With Image") ReadReceiptCell(readReceipt: .init(userID: "@test:matrix.org", formattedTimestamp: "10:00"), memberState: nil, - imageProvider: MockMediaProvider()) + mediaProvider: MockMediaProvider()) .previewDisplayName("Loading Member") } } diff --git a/ElementX/Sources/Screens/Timeline/View/ReadReceipts/ReadReceiptsSummaryView.swift b/ElementX/Sources/Screens/Timeline/View/ReadReceipts/ReadReceiptsSummaryView.swift index 5673860a7..8e5e9e45c 100644 --- a/ElementX/Sources/Screens/Timeline/View/ReadReceipts/ReadReceiptsSummaryView.swift +++ b/ElementX/Sources/Screens/Timeline/View/ReadReceipts/ReadReceiptsSummaryView.swift @@ -31,7 +31,7 @@ struct ReadReceiptsSummaryView: View { ForEach(orderedReadReceipts) { receipt in ReadReceiptCell(readReceipt: receipt, memberState: context.viewState.members[receipt.userID], - imageProvider: context.imageProvider) + mediaProvider: context.mediaProvider) } } } diff --git a/ElementX/Sources/Screens/Timeline/View/Replies/TimelineReplyView.swift b/ElementX/Sources/Screens/Timeline/View/Replies/TimelineReplyView.swift index 31829c5fe..754ce80b4 100644 --- a/ElementX/Sources/Screens/Timeline/View/Replies/TimelineReplyView.swift +++ b/ElementX/Sources/Screens/Timeline/View/Replies/TimelineReplyView.swift @@ -164,7 +164,7 @@ struct TimelineReplyView: View { LoadableImage(mediaSource: mediaSource, size: .init(width: imageContainerSize, height: imageContainerSize), - imageProvider: context.imageProvider) { + mediaProvider: context.mediaProvider) { Image(systemName: "photo") .padding(4.0) } diff --git a/ElementX/Sources/Screens/Timeline/View/Supplementary/ReactionsSummaryView.swift b/ElementX/Sources/Screens/Timeline/View/Supplementary/ReactionsSummaryView.swift index 53fbfc0e3..e64f18cd8 100644 --- a/ElementX/Sources/Screens/Timeline/View/Supplementary/ReactionsSummaryView.swift +++ b/ElementX/Sources/Screens/Timeline/View/Supplementary/ReactionsSummaryView.swift @@ -19,7 +19,7 @@ import SwiftUI struct ReactionsSummaryView: View { let reactions: [AggregatedReaction] let members: [String: RoomMemberState] - let imageProvider: ImageProviderProtocol? + let mediaProvider: MediaProviderProtocol? @State var selectedReactionKey: String @@ -64,7 +64,7 @@ struct ReactionsSummaryView: View { ScrollView { VStack(alignment: .leading, spacing: 8) { ForEach(reaction.senders) { sender in - ReactionSummarySenderView(sender: sender, member: members[sender.id], imageProvider: imageProvider) + ReactionSummarySenderView(sender: sender, member: members[sender.id], mediaProvider: mediaProvider) .padding(.horizontal, 16) } } @@ -110,7 +110,7 @@ private struct ReactionSummaryButton: View { private struct ReactionSummarySenderView: View { var sender: ReactionSender var member: RoomMemberState? - let imageProvider: ImageProviderProtocol? + let mediaProvider: MediaProviderProtocol? var displayName: String { member?.displayName ?? sender.id @@ -122,7 +122,7 @@ private struct ReactionSummarySenderView: View { name: displayName, contentID: sender.id, avatarSize: .user(on: .timeline), - imageProvider: imageProvider) + mediaProvider: mediaProvider) VStack(alignment: .leading, spacing: 0) { HStack(spacing: 8) { @@ -147,7 +147,7 @@ struct ReactionsSummaryView_Previews: PreviewProvider, TestablePreview { static var previews: some View { ReactionsSummaryView(reactions: AggregatedReaction.mockReactions, members: [:], - imageProvider: MockMediaProvider(), + mediaProvider: MockMediaProvider(), selectedReactionKey: AggregatedReaction.mockReactions[0].key) } } diff --git a/ElementX/Sources/Screens/Timeline/View/Supplementary/TimelineReadReceiptsView.swift b/ElementX/Sources/Screens/Timeline/View/Supplementary/TimelineReadReceiptsView.swift index 2a82468b0..4eeac31e7 100644 --- a/ElementX/Sources/Screens/Timeline/View/Supplementary/TimelineReadReceiptsView.swift +++ b/ElementX/Sources/Screens/Timeline/View/Supplementary/TimelineReadReceiptsView.swift @@ -31,7 +31,7 @@ struct TimelineReadReceiptsView: View { name: context.viewState.members[receipt.userID]?.displayName, contentID: receipt.userID, avatarSize: .user(on: .readReceipt), - imageProvider: context.imageProvider) + mediaProvider: context.mediaProvider) .overlay { RoundedRectangle(cornerRadius: .infinity) .stroke(Color.compound.bgCanvasDefault, lineWidth: 1) diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/CallNotificationRoomTimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/CallNotificationRoomTimelineView.swift index 3b07f9e12..4e9f8f24c 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/CallNotificationRoomTimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/CallNotificationRoomTimelineView.swift @@ -29,7 +29,7 @@ struct CallNotificationRoomTimelineView: View { name: timelineItem.sender.displayName ?? timelineItem.sender.id, contentID: timelineItem.sender.id, avatarSize: .user(on: .timeline), - imageProvider: context.imageProvider) + mediaProvider: context.mediaProvider) .accessibilityHidden(true) VStack(alignment: .leading, spacing: 0) { diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/ImageRoomTimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/ImageRoomTimelineView.swift index 1490c5921..fcf12babb 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/ImageRoomTimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/ImageRoomTimelineView.swift @@ -25,7 +25,7 @@ struct ImageRoomTimelineView: View { TimelineStyler(timelineItem: timelineItem) { LoadableImage(mediaSource: source, blurhash: timelineItem.content.blurhash, - imageProvider: context.imageProvider) { + mediaProvider: context.mediaProvider) { placeholder } .timelineMediaFrame(height: timelineItem.content.height, diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/StickerRoomTimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/StickerRoomTimelineView.swift index 69582327d..dacd0f179 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/StickerRoomTimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/StickerRoomTimelineView.swift @@ -25,7 +25,7 @@ struct StickerRoomTimelineView: View { TimelineStyler(timelineItem: timelineItem) { LoadableImage(url: timelineItem.imageURL, blurhash: timelineItem.blurhash, - imageProvider: context.imageProvider) { + mediaProvider: context.mediaProvider) { placeholder } .timelineMediaFrame(height: timelineItem.height, diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/VideoRoomTimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/VideoRoomTimelineView.swift index 3612c9a49..22f17cdaa 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/VideoRoomTimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineItemViews/VideoRoomTimelineView.swift @@ -36,7 +36,7 @@ struct VideoRoomTimelineView: View { if let thumbnailSource = timelineItem.content.thumbnailSource { LoadableImage(mediaSource: thumbnailSource, blurhash: timelineItem.content.blurhash, - imageProvider: context.imageProvider) { imageView in + mediaProvider: context.mediaProvider) { imageView in imageView .overlay { playIcon } } placeholder: { diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineSenderAvatarView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineSenderAvatarView.swift index 173d6ce9d..98ba7cb8c 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineSenderAvatarView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineSenderAvatarView.swift @@ -27,7 +27,7 @@ struct TimelineSenderAvatarView: View { name: timelineItem.sender.displayName, contentID: timelineItem.sender.id, avatarSize: .user(on: .timeline), - imageProvider: context.imageProvider) + mediaProvider: context.mediaProvider) .overlay { Circle().stroke(Color.compound.bgCanvasDefault, lineWidth: 3) } diff --git a/ElementX/Sources/Screens/UserProfileScreen/UserProfileScreenViewModel.swift b/ElementX/Sources/Screens/UserProfileScreen/UserProfileScreenViewModel.swift index 63d87be19..1dafa9c70 100644 --- a/ElementX/Sources/Screens/UserProfileScreen/UserProfileScreenViewModel.swift +++ b/ElementX/Sources/Screens/UserProfileScreen/UserProfileScreenViewModel.swift @@ -47,7 +47,7 @@ class UserProfileScreenViewModel: UserProfileScreenViewModelType, UserProfileScr isPresentedModally: isPresentedModally, bindings: .init()) - super.init(initialViewState: initialViewState, imageProvider: mediaProvider) + super.init(initialViewState: initialViewState, mediaProvider: mediaProvider) showLoadingIndicator(allowsInteraction: true) Task { diff --git a/ElementX/Sources/Screens/UserProfileScreen/View/UserProfileScreen.swift b/ElementX/Sources/Screens/UserProfileScreen/View/UserProfileScreen.swift index a325c289e..69828a5f6 100644 --- a/ElementX/Sources/Screens/UserProfileScreen/View/UserProfileScreen.swift +++ b/ElementX/Sources/Screens/UserProfileScreen/View/UserProfileScreen.swift @@ -72,7 +72,7 @@ struct UserProfileScreen: View { if let userProfile = context.viewState.userProfile { AvatarHeaderView(user: userProfile, avatarSize: .user(on: .memberDetails), - imageProvider: context.imageProvider) { + mediaProvider: context.mediaProvider) { context.send(viewAction: .displayAvatar) } footer: { otherUserFooter @@ -80,7 +80,7 @@ struct UserProfileScreen: View { } else { AvatarHeaderView(user: UserProfileProxy(userID: context.viewState.userID), avatarSize: .user(on: .memberDetails), - imageProvider: context.imageProvider, + mediaProvider: context.mediaProvider, footer: { }) } } diff --git a/ElementX/Sources/Services/Media/Provider/ImageProviderProtocol.swift b/ElementX/Sources/Services/Media/Provider/ImageProviderProtocol.swift deleted file mode 100644 index 345726106..000000000 --- a/ElementX/Sources/Services/Media/Provider/ImageProviderProtocol.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright 2023 New Vector Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import UIKit - -protocol ImageProviderProtocol { - func imageFromSource(_ source: MediaSourceProxy?, size: CGSize?) -> UIImage? - - func loadImageFromSource(_ source: MediaSourceProxy, size: CGSize?) async -> Result - - func loadImageDataFromSource(_ source: MediaSourceProxy) async -> Result -} - -extension ImageProviderProtocol { - func imageFromSource(_ source: MediaSourceProxy?) -> UIImage? { - imageFromSource(source, size: nil) - } -} diff --git a/ElementX/Sources/Services/Media/Provider/MediaLoaderProtocol.swift b/ElementX/Sources/Services/Media/Provider/MediaLoaderProtocol.swift index a620c1975..e8fb855ce 100644 --- a/ElementX/Sources/Services/Media/Provider/MediaLoaderProtocol.swift +++ b/ElementX/Sources/Services/Media/Provider/MediaLoaderProtocol.swift @@ -16,6 +16,7 @@ import Foundation +// sourcery: AutoMockable protocol MediaLoaderProtocol { func loadMediaContentForSource(_ source: MediaSourceProxy) async throws -> Data diff --git a/ElementX/Sources/Services/Media/Provider/MediaProvider.swift b/ElementX/Sources/Services/Media/Provider/MediaProvider.swift index 55877f2a4..0df6643b8 100644 --- a/ElementX/Sources/Services/Media/Provider/MediaProvider.swift +++ b/ElementX/Sources/Services/Media/Provider/MediaProvider.swift @@ -14,17 +14,21 @@ // limitations under the License. // +import Combine import Kingfisher import UIKit struct MediaProvider: MediaProviderProtocol { private let mediaLoader: MediaLoaderProtocol private let imageCache: Kingfisher.ImageCache + private let networkMonitor: NetworkMonitorProtocol? init(mediaLoader: MediaLoaderProtocol, - imageCache: Kingfisher.ImageCache) { + imageCache: Kingfisher.ImageCache, + networkMonitor: NetworkMonitorProtocol?) { self.mediaLoader = mediaLoader self.imageCache = imageCache + self.networkMonitor = networkMonitor } // MARK: Images @@ -71,6 +75,45 @@ struct MediaProvider: MediaProviderProtocol { } } + func loadImageRetryingOnReconnection(_ source: MediaSourceProxy, size: CGSize?) -> Task { + guard let networkMonitor else { + fatalError("This method shouldn't be invoked without a NetworkMonitor set.") + } + + return Task { + if case let .success(image) = await loadImageFromSource(source, size: size) { + return image + } + + guard !Task.isCancelled else { + throw MediaProviderError.cancelled + } + + for await reachability in networkMonitor.reachabilityPublisher.values { + guard !Task.isCancelled else { + throw MediaProviderError.cancelled + } + + guard reachability == .reachable else { + continue + } + + switch await loadImageFromSource(source, size: size) { + case .success(let image): + return image + case .failure: + // If it fails after a retry with the network available + // then something else must be wrong. Bail out. + if reachability == .reachable { + throw MediaProviderError.cancelled + } + } + } + + throw MediaProviderError.cancelled + } + } + func loadImageDataFromSource(_ source: MediaSourceProxy) async -> Result { do { let imageData = try await mediaLoader.loadMediaContentForSource(source) diff --git a/ElementX/Sources/Services/Media/Provider/MediaProviderProtocol.swift b/ElementX/Sources/Services/Media/Provider/MediaProviderProtocol.swift index 2bc47764d..baeccd0b6 100644 --- a/ElementX/Sources/Services/Media/Provider/MediaProviderProtocol.swift +++ b/ElementX/Sources/Services/Media/Provider/MediaProviderProtocol.swift @@ -22,14 +22,29 @@ enum MediaProviderError: Error { case failedRetrievingFile case invalidImageData case failedRetrievingThumbnail + case cancelled } -protocol MediaProviderProtocol: ImageProviderProtocol { - func loadFileFromSource(_ source: MediaSourceProxy, body: String?) async -> Result +protocol MediaProviderProtocol { + func imageFromSource(_ source: MediaSourceProxy?, size: CGSize?) -> UIImage? + func loadImageFromSource(_ source: MediaSourceProxy, size: CGSize?) async -> Result + func loadImageDataFromSource(_ source: MediaSourceProxy) async -> Result + func loadImageRetryingOnReconnection(_ source: MediaSourceProxy, size: CGSize?) -> Task + func loadThumbnailForSource(source: MediaSourceProxy, size: CGSize) async -> Result + + func loadFileFromSource(_ source: MediaSourceProxy, body: String?) async -> Result } extension MediaProviderProtocol { + func imageFromSource(_ source: MediaSourceProxy?) -> UIImage? { + imageFromSource(source, size: nil) + } + + func loadImageRetryingOnReconnection(_ source: MediaSourceProxy) -> Task { + loadImageRetryingOnReconnection(source, size: nil) + } + func loadFileFromSource(_ source: MediaSourceProxy) async -> Result { await loadFileFromSource(source, body: nil) } diff --git a/ElementX/Sources/Services/Media/Provider/MockMediaProvider.swift b/ElementX/Sources/Services/Media/Provider/MockMediaProvider.swift index 63064a247..cb7c43891 100644 --- a/ElementX/Sources/Services/Media/Provider/MockMediaProvider.swift +++ b/ElementX/Sources/Services/Media/Provider/MockMediaProvider.swift @@ -58,4 +58,14 @@ struct MockMediaProvider: MediaProviderProtocol { } return .failure(.failedRetrievingFile) } + + func loadImageRetryingOnReconnection(_ source: MediaSourceProxy, size: CGSize?) -> Task { + Task { + guard let image = UIImage(systemName: "photo") else { + fatalError() + } + + return image + } + } } diff --git a/ElementX/Sources/Services/UserSession/UserSessionStore.swift b/ElementX/Sources/Services/UserSession/UserSessionStore.swift index c987e708f..b3d37345a 100644 --- a/ElementX/Sources/Services/UserSession/UserSessionStore.swift +++ b/ElementX/Sources/Services/UserSession/UserSessionStore.swift @@ -111,7 +111,8 @@ class UserSessionStore: UserSessionStoreProtocol { private func buildUserSessionWithClient(_ clientProxy: ClientProxyProtocol) -> UserSessionProtocol { let mediaProvider = MediaProvider(mediaLoader: clientProxy, - imageCache: .onlyInMemory) + imageCache: .onlyInMemory, + networkMonitor: networkMonitor) let voiceMessageMediaManager = VoiceMessageMediaManager(mediaProvider: mediaProvider) diff --git a/NSE/Sources/Other/NSEUserSession.swift b/NSE/Sources/Other/NSEUserSession.swift index a23f15a6c..3f17b810d 100644 --- a/NSE/Sources/Other/NSEUserSession.swift +++ b/NSE/Sources/Other/NSEUserSession.swift @@ -22,7 +22,8 @@ final class NSEUserSession { private let notificationClient: NotificationClient private let userID: String private(set) lazy var mediaProvider: MediaProviderProtocol = MediaProvider(mediaLoader: MediaLoader(client: baseClient), - imageCache: .onlyOnDisk) + imageCache: .onlyOnDisk, + networkMonitor: nil) private let delegateHandle: TaskHandle? init(credentials: KeychainCredentials, clientSessionDelegate: ClientSessionDelegate, simplifiedSlidingSyncEnabled: Bool, appHooks: AppHooks) async throws { diff --git a/NSE/SupportingFiles/target.yml b/NSE/SupportingFiles/target.yml index 8de457a68..41d2741d5 100644 --- a/NSE/SupportingFiles/target.yml +++ b/NSE/SupportingFiles/target.yml @@ -73,8 +73,12 @@ targets: sources: - path: ../Sources - path: ../SupportingFiles + - path: ../../ElementX/Sources/AppHooks/AppHooks.swift + - path: ../../ElementX/Sources/AppHooks/Hooks/ClientBuilderHook.swift + - path: ../../ElementX/Sources/Application/AppSettings.swift - path: ../../ElementX/Sources/Generated - path: ../../ElementX/Sources/Other/AvatarSize.swift + - path: ../../ElementX/Sources/Other/CurrentValuePublisher.swift - path: ../../ElementX/Sources/Other/Extensions/AttributedString.swift - path: ../../ElementX/Sources/Other/Extensions/Bundle.swift - path: ../../ElementX/Sources/Other/Extensions/ClientBuilder.swift @@ -92,12 +96,14 @@ targets: - path: ../../ElementX/Sources/Other/InfoPlistReader.swift - path: ../../ElementX/Sources/Other/Logging - path: ../../ElementX/Sources/Other/MatrixEntityRegex.swift + - path: ../../ElementX/Sources/Other/NetworkMonitor + - path: ../../ElementX/Sources/Other/Pills/PillConstants.swift + - path: ../../ElementX/Sources/Other/Pills/PlainMentionBuilder.swift - path: ../../ElementX/Sources/Other/SwiftUI/Views/PlaceholderAvatarImage.swift + - path: ../../ElementX/Sources/Other/TestablePreview.swift - path: ../../ElementX/Sources/Other/UserAgentBuilder.swift - path: ../../ElementX/Sources/Other/UserPreference.swift - - path: ../../ElementX/Sources/Other/TestablePreview.swift - - path: ../../ElementX/Sources/Other/Pills/PlainMentionBuilder.swift - - path: ../../ElementX/Sources/Other/Pills/PillConstants.swift + - path: ../../ElementX/Sources/Services/ElementCall/ElementCallServiceConstants.swift - path: ../../ElementX/Sources/Services/Keychain/KeychainController.swift - path: ../../ElementX/Sources/Services/Keychain/KeychainControllerProtocol.swift - path: ../../ElementX/Sources/Services/Media/Provider @@ -105,7 +111,3 @@ targets: - path: ../../ElementX/Sources/Services/Notification/Proxy - path: ../../ElementX/Sources/Services/Room/RoomSummary/RoomMessageEventStringBuilder.swift - path: ../../ElementX/Sources/Services/UserSession/RestorationToken.swift - - path: ../../ElementX/Sources/Services/ElementCall/ElementCallServiceConstants.swift - - path: ../../ElementX/Sources/Application/AppSettings.swift - - path: ../../ElementX/Sources/AppHooks/AppHooks.swift - - path: ../../ElementX/Sources/AppHooks/Hooks/ClientBuilderHook.swift diff --git a/UnitTests/Sources/GlobalSearchScreenViewModelTests.swift b/UnitTests/Sources/GlobalSearchScreenViewModelTests.swift index 3f796a25f..40c9a36d9 100644 --- a/UnitTests/Sources/GlobalSearchScreenViewModelTests.swift +++ b/UnitTests/Sources/GlobalSearchScreenViewModelTests.swift @@ -28,7 +28,7 @@ class GlobalSearchScreenViewModelTests: XCTestCase { override func setUpWithError() throws { cancellables.removeAll() viewModel = GlobalSearchScreenViewModel(roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(.mockRooms))), - imageProvider: MockMediaProvider()) + mediaProvider: MockMediaProvider()) context = viewModel.context } diff --git a/UnitTests/Sources/MediaProvider/MediaProviderTests.swift b/UnitTests/Sources/MediaProvider/MediaProviderTests.swift index fd45c2874..19e6a0349 100644 --- a/UnitTests/Sources/MediaProvider/MediaProviderTests.swift +++ b/UnitTests/Sources/MediaProvider/MediaProviderTests.swift @@ -15,20 +15,70 @@ // @testable import ElementX + +import Combine import Kingfisher import XCTest @MainActor final class MediaProviderTests: XCTestCase { - private let mediaLoader = MockMediaLoader() + private var mediaLoader: MediaLoaderMock! private var imageCache: MockImageCache! + private var networkMonitor: NetworkMonitorMock! var mediaProvider: MediaProvider! override func setUp() { + mediaLoader = MediaLoaderMock() imageCache = MockImageCache(name: "Test") + networkMonitor = NetworkMonitorMock() + mediaProvider = MediaProvider(mediaLoader: mediaLoader, - imageCache: imageCache) + imageCache: imageCache, + networkMonitor: networkMonitor) + } + + func testLoadingRetriedOnReconnection() async throws { + let testImage = try loadTestImage() + guard let pngData = testImage.pngData() else { + XCTFail("Test image should contain valid .png data") + return + } + + let loadTask = mediaProvider.loadImageRetryingOnReconnection(MediaSourceProxy(url: URL.picturesDirectory, mimeType: "image/jpeg")) + + let connectivitySubject = CurrentValueSubject(.unreachable) + + mediaLoader.loadMediaContentForSourceClosure = { _ in + switch connectivitySubject.value { + case .unreachable: + connectivitySubject.send(.reachable) + throw MediaProviderTestsError.error + case .reachable: + return pngData + } + } + + networkMonitor.underlyingReachabilityPublisher = connectivitySubject.asCurrentValuePublisher() + + let result = try? await loadTask.value + + XCTAssertNotNil(result) + XCTAssertEqual(mediaLoader.loadMediaContentForSourceCallsCount, 2) + } + + func testLoadingRetriedOnReconnectionCancelsAfterSecondFailure() async throws { + let loadTask = mediaProvider.loadImageRetryingOnReconnection(MediaSourceProxy(url: URL.picturesDirectory, mimeType: "image/jpeg")) + + let connectivitySubject = CurrentValueSubject(.reachable) + + mediaLoader.loadMediaContentForSourceThrowableError = MediaProviderTestsError.error + + networkMonitor.underlyingReachabilityPublisher = connectivitySubject.asCurrentValuePublisher() + + let result = try? await loadTask.value + + XCTAssertNil(result) } func test_whenImageFromSourceWithSourceNil_nilReturned() throws { @@ -78,7 +128,9 @@ final class MediaProviderTests: XCTestCase { func test_whenLoadImageFromSourceAndImageNotCachedAndRetrieveImageFails_imageThumbnailIsLoaded() async throws { let avatarSize = AvatarSize.room(on: .timeline) let expectedImage = try loadTestImage() - mediaLoader.mediaThumbnailData = expectedImage.pngData() + + mediaLoader.loadMediaThumbnailForSourceWidthHeightReturnValue = expectedImage.pngData() + let result = await mediaProvider.loadImageFromSource(MediaSourceProxy(url: URL.picturesDirectory, mimeType: "image/jpeg"), size: avatarSize.scaledSize) switch result { @@ -94,7 +146,9 @@ final class MediaProviderTests: XCTestCase { let url = URL.picturesDirectory let key = "\(url.absoluteString){\(avatarSize.scaledValue),\(avatarSize.scaledValue)}" let expectedImage = try loadTestImage() - mediaLoader.mediaThumbnailData = expectedImage.pngData() + + mediaLoader.loadMediaThumbnailForSourceWidthHeightReturnValue = expectedImage.pngData() + _ = await mediaProvider.loadImageFromSource(MediaSourceProxy(url: url, mimeType: "image/jpeg"), size: avatarSize.scaledSize) let storedImage = try XCTUnwrap(imageCache.storedImages[key]) @@ -103,7 +157,9 @@ final class MediaProviderTests: XCTestCase { func test_whenLoadImageFromSourceAndImageNotCachedAndRetrieveImageFailsAndNoAvatarSize_imageContentIsLoaded() async throws { let expectedImage = try loadTestImage() - mediaLoader.mediaContentData = expectedImage.pngData() + + mediaLoader.loadMediaContentForSourceReturnValue = expectedImage.pngData() + let result = await mediaProvider.loadImageFromSource(MediaSourceProxy(url: URL.picturesDirectory, mimeType: "image/jpeg"), size: nil) switch result { @@ -115,6 +171,8 @@ final class MediaProviderTests: XCTestCase { } func test_whenLoadImageFromSourceAndImageNotCachedAndRetrieveImageFailsAndLoadImageThumbnailFails_errorIsThrown() async throws { + mediaLoader.loadMediaThumbnailForSourceWidthHeightThrowableError = MediaProviderTestsError.error + let result = await mediaProvider.loadImageFromSource(MediaSourceProxy(url: URL.picturesDirectory, mimeType: "image/jpeg"), size: AvatarSize.room(on: .timeline).scaledSize) switch result { @@ -126,6 +184,8 @@ final class MediaProviderTests: XCTestCase { } func test_whenLoadImageFromSourceAndImageNotCachedAndRetrieveImageFailsAndNoAvatarSizeAndLoadImageContentFails_errorIsThrown() async throws { + mediaLoader.loadMediaContentForSourceThrowableError = MediaProviderTestsError.error + let result = await mediaProvider.loadImageFromSource(MediaSourceProxy(url: URL.picturesDirectory, mimeType: "image/jpeg"), size: nil) switch result { @@ -137,7 +197,8 @@ final class MediaProviderTests: XCTestCase { } func test_whenLoadImageFromSourceAndImageNotCachedAndRetrieveImageFailsAndImageThumbnailIsLoadedWithCorruptedData_errorIsThrown() async throws { - mediaLoader.mediaThumbnailData = Data() + mediaLoader.loadMediaThumbnailForSourceWidthHeightReturnValue = Data() + let result = await mediaProvider.loadImageFromSource(MediaSourceProxy(url: URL.picturesDirectory, mimeType: "image/jpeg"), size: AvatarSize.room(on: .timeline).scaledSize) switch result { @@ -148,30 +209,15 @@ final class MediaProviderTests: XCTestCase { } } - func test_whenLoadFileFromSourceAndFileFromSourceExists_urlIsReturned() async throws { - let expectedURL = URL(filePath: "/some/file/path") - let expectedResult: Result = .success(.unmanaged(url: expectedURL)) - mediaLoader.mediaFileURL = expectedURL - let result = await mediaProvider.loadFileFromSource(MediaSourceProxy(url: "test/test1", mimeType: "video/mp4")) - XCTAssertEqual(result, expectedResult) - } - - func test_whenLoadFileFromSourceAndNoFileFromSourceExistsAndLoadContentSourceFails_failureIsReturned() async throws { - let expectedResult: Result = .failure(.failedRetrievingFile) - mediaLoader.mediaFileURL = nil - let result = await mediaProvider.loadFileFromSource(MediaSourceProxy(url: "test/test1", mimeType: "video/mp4")) - XCTAssertEqual(result, expectedResult) - } - private func loadTestImage() throws -> UIImage { guard let path = Bundle(for: Self.self).path(forResource: "test_image", ofType: "png"), let image = UIImage(contentsOfFile: path) else { - throw MediaProviderTestsError.screenshotNotFound + throw MediaProviderTestsError.error } return image } } -enum MediaProviderTestsError: Error { - case screenshotNotFound +private enum MediaProviderTestsError: Error { + case error } diff --git a/UnitTests/Sources/MediaProvider/MockMediaLoader.swift b/UnitTests/Sources/MediaProvider/MockMediaLoader.swift deleted file mode 100644 index a2270f88e..000000000 --- a/UnitTests/Sources/MediaProvider/MockMediaLoader.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// Copyright 2022 New Vector Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -@testable import ElementX -import Foundation - -enum MockMediaLoaderError: Error { - case someError -} - -class MockMediaLoader: MediaLoaderProtocol { - var mediaContentData: Data? - var mediaThumbnailData: Data? - var mediaFileURL: URL? - - func loadMediaContentForSource(_ source: ElementX.MediaSourceProxy) async throws -> Data { - if let mediaContentData { - return mediaContentData - } else { - throw MockMediaLoaderError.someError - } - } - - func loadMediaThumbnailForSource(_ source: ElementX.MediaSourceProxy, width: UInt, height: UInt) async throws -> Data { - if let mediaThumbnailData { - return mediaThumbnailData - } else { - throw MockMediaLoaderError.someError - } - } - - func loadMediaFileForSource(_ source: MediaSourceProxy, body: String?) async throws -> MediaFileHandleProxy { - if let mediaFileURL { - return .unmanaged(url: mediaFileURL) - } else { - throw MockMediaLoaderError.someError - } - } -}