diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 0d4461dc1..327062dc1 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 182BC48127C4EBBB00A30C33 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 182BC48027C4EBBB00A30C33 /* Kingfisher */; }; + 183E023227E4A3CF00903BED /* PlaceholderAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 183E023127E4A3CF00903BED /* PlaceholderAvatarImage.swift */; }; 184B31DF27D898960075A669 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 184B31DE27D898960075A669 /* Introspect */; }; 1850253F27B6918D002E6B18 /* ElementXTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1850253E27B6918D002E6B18 /* ElementXTests.swift */; }; 1850254927B6918D002E6B18 /* ElementXUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1850254827B6918D002E6B18 /* ElementXUITests.swift */; }; @@ -18,7 +19,6 @@ 1850257127B6A135002E6B18 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1850256927B6A135002E6B18 /* LaunchScreen.storyboard */; }; 1863A3FC27BA5A9100B52E4D /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 1863A3FB27BA5A9100B52E4D /* KeychainAccess */; }; 1863A40627BA6DFC00B52E4D /* SwiftyBeaver in Frameworks */ = {isa = PBXBuildFile; productRef = 1863A40527BA6DFC00B52E4D /* SwiftyBeaver */; }; - 18A318DD27DA42C9000867CD /* RoomTimelineViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A318DB27DA42C9000867CD /* RoomTimelineViewProvider.swift */; }; 18C5744627E11F1900D70937 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 18C5744527E11F1900D70937 /* MatrixRustSDK */; }; 18C5744C27E1D84000D70937 /* RoomProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C5744827E1D84000D70937 /* RoomProxyProtocol.swift */; }; 18C5744D27E1D84000D70937 /* RoomProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C5744A27E1D84000D70937 /* RoomProxy.swift */; }; @@ -27,12 +27,23 @@ 18C5745227E1D88600D70937 /* ImageRoomMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C5745127E1D88600D70937 /* ImageRoomMessage.swift */; }; 18C5745427E1D88E00D70937 /* TextRoomMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C5745327E1D88E00D70937 /* TextRoomMessage.swift */; }; 18C5745627E1DCA800D70937 /* RoomMessageFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C5745527E1DCA800D70937 /* RoomMessageFactory.swift */; }; - 18C5745827E1EB6E00D70937 /* RoomTimelineItemFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C5745727E1EB6E00D70937 /* RoomTimelineItemFactory.swift */; }; - 18DF7C2A27E23E3A00291672 /* RoomTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18DF7C2927E23E3A00291672 /* RoomTimelineItemProtocol.swift */; }; - 18DF7C2C27E23EC000291672 /* RoomTimelineViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18DF7C2B27E23EC000291672 /* RoomTimelineViewFactory.swift */; }; 18DF7C2F27E264FC00291672 /* MediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18DF7C2E27E264FC00291672 /* MediaProvider.swift */; }; 18DF7C3127E3608100291672 /* MediaProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18DF7C3027E3608100291672 /* MediaProviderProtocol.swift */; }; 18DF7C3327E3608800291672 /* MockMediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18DF7C3227E3608800291672 /* MockMediaProvider.swift */; }; + 18DF7C4127E4670600291672 /* RoomTimelineViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18DF7C3527E4670600291672 /* RoomTimelineViewProvider.swift */; }; + 18DF7C4227E4670600291672 /* RoomTimelineItemFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18DF7C3627E4670600291672 /* RoomTimelineItemFactory.swift */; }; + 18DF7C4327E4670600291672 /* RoomTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18DF7C3727E4670600291672 /* RoomTimelineItemProtocol.swift */; }; + 18DF7C4427E4670600291672 /* RoomTimelineViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18DF7C3827E4670600291672 /* RoomTimelineViewFactory.swift */; }; + 18DF7C4527E4670600291672 /* SeparatorRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18DF7C3A27E4670600291672 /* SeparatorRoomTimelineItem.swift */; }; + 18DF7C4627E4670600291672 /* ImageRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18DF7C3B27E4670600291672 /* ImageRoomTimelineItem.swift */; }; + 18DF7C4727E4670600291672 /* TextRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18DF7C3C27E4670600291672 /* TextRoomTimelineItem.swift */; }; + 18DF7C4827E4670600291672 /* ImageRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18DF7C3E27E4670600291672 /* ImageRoomTimelineView.swift */; }; + 18DF7C4927E4670600291672 /* SeparatorRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18DF7C3F27E4670600291672 /* SeparatorRoomTimelineView.swift */; }; + 18DF7C4A27E4670600291672 /* TextRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18DF7C4027E4670600291672 /* TextRoomTimelineView.swift */; }; + 18DF7C4C27E4672C00291672 /* EventBasedTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18DF7C4B27E4672C00291672 /* EventBasedTimelineItemProtocol.swift */; }; + 18DF7C4E27E4673E00291672 /* DecorationTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18DF7C4D27E4673E00291672 /* DecorationTimelineItemProtocol.swift */; }; + 18DF7C5027E46A7A00291672 /* EventBasedTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18DF7C4F27E46A7A00291672 /* EventBasedTimelineView.swift */; }; + 18DF7C5327E4754500291672 /* MemberDetailsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18DF7C5227E4754500291672 /* MemberDetailsProvider.swift */; }; 18F2BADA27D25B4000DD1988 /* RoomTimelineProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA7727D25B4000DD1988 /* RoomTimelineProvider.swift */; }; 18F2BADB27D25B4000DD1988 /* AuthenticationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA7927D25B4000DD1988 /* AuthenticationCoordinator.swift */; }; 18F2BADC27D25B4000DD1988 /* UserSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA7A27D25B4000DD1988 /* UserSession.swift */; }; @@ -90,12 +101,6 @@ 18F2BB2227D25D4600DD1988 /* LoginScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BACF27D25B4000DD1988 /* LoginScreenUITests.swift */; }; 18F2BB2827D2647A00DD1988 /* MockRoomTimelineController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BB2727D2647A00DD1988 /* MockRoomTimelineController.swift */; }; 18F2BB2A27D2648900DD1988 /* RoomTimelineControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BB2927D2648900DD1988 /* RoomTimelineControllerProtocol.swift */; }; - 18F9889827DB7473002F48B4 /* ImageRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F9889727DB7473002F48B4 /* ImageRoomTimelineItem.swift */; }; - 18F9889A27DB747B002F48B4 /* TextRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F9889927DB747B002F48B4 /* TextRoomTimelineItem.swift */; }; - 18F9889C27DB7491002F48B4 /* SeparatorRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F9889B27DB7491002F48B4 /* SeparatorRoomTimelineItem.swift */; }; - 18F9889E27DB752B002F48B4 /* ImageRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F9889D27DB752B002F48B4 /* ImageRoomTimelineView.swift */; }; - 18F988A027DB7532002F48B4 /* TextRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F9889F27DB7532002F48B4 /* TextRoomTimelineView.swift */; }; - 18F988A227DB753B002F48B4 /* SeparatorRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F988A127DB753B002F48B4 /* SeparatorRoomTimelineView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -117,6 +122,7 @@ /* Begin PBXFileReference section */ 181716DC27E11EF1002B8E3F /* matrix-rust-components-swift */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "matrix-rust-components-swift"; path = "../matrix-rust-components-swift"; sourceTree = ""; }; + 183E023127E4A3CF00903BED /* PlaceholderAvatarImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PlaceholderAvatarImage.swift; path = ../../../../../../../../Desktop/PlaceholderAvatarImage.swift; sourceTree = ""; }; 1850252427B6918C002E6B18 /* ElementX.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ElementX.app; sourceTree = BUILT_PRODUCTS_DIR; }; 1850253A27B6918D002E6B18 /* ElementXTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ElementXTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 1850253E27B6918D002E6B18 /* ElementXTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementXTests.swift; sourceTree = ""; }; @@ -128,7 +134,6 @@ 1850256727B6A135002E6B18 /* ElementX.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = ElementX.entitlements; sourceTree = ""; }; 1850256827B6A135002E6B18 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 1850256A27B6A135002E6B18 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 18A318DB27DA42C9000867CD /* RoomTimelineViewProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomTimelineViewProvider.swift; sourceTree = ""; }; 18C5744827E1D84000D70937 /* RoomProxyProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomProxyProtocol.swift; sourceTree = ""; }; 18C5744A27E1D84000D70937 /* RoomProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomProxy.swift; sourceTree = ""; }; 18C5744B27E1D84000D70937 /* MockRoomProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockRoomProxy.swift; sourceTree = ""; }; @@ -136,12 +141,23 @@ 18C5745127E1D88600D70937 /* ImageRoomMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRoomMessage.swift; sourceTree = ""; }; 18C5745327E1D88E00D70937 /* TextRoomMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomMessage.swift; sourceTree = ""; }; 18C5745527E1DCA800D70937 /* RoomMessageFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMessageFactory.swift; sourceTree = ""; }; - 18C5745727E1EB6E00D70937 /* RoomTimelineItemFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemFactory.swift; sourceTree = ""; }; - 18DF7C2927E23E3A00291672 /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = ""; }; - 18DF7C2B27E23EC000291672 /* RoomTimelineViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineViewFactory.swift; sourceTree = ""; }; 18DF7C2E27E264FC00291672 /* MediaProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProvider.swift; sourceTree = ""; }; 18DF7C3027E3608100291672 /* MediaProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProviderProtocol.swift; sourceTree = ""; }; 18DF7C3227E3608800291672 /* MockMediaProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockMediaProvider.swift; sourceTree = ""; }; + 18DF7C3527E4670600291672 /* RoomTimelineViewProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomTimelineViewProvider.swift; sourceTree = ""; }; + 18DF7C3627E4670600291672 /* RoomTimelineItemFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemFactory.swift; sourceTree = ""; }; + 18DF7C3727E4670600291672 /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = ""; }; + 18DF7C3827E4670600291672 /* RoomTimelineViewFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomTimelineViewFactory.swift; sourceTree = ""; }; + 18DF7C3A27E4670600291672 /* SeparatorRoomTimelineItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeparatorRoomTimelineItem.swift; sourceTree = ""; }; + 18DF7C3B27E4670600291672 /* ImageRoomTimelineItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageRoomTimelineItem.swift; sourceTree = ""; }; + 18DF7C3C27E4670600291672 /* TextRoomTimelineItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineItem.swift; sourceTree = ""; }; + 18DF7C3E27E4670600291672 /* ImageRoomTimelineView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageRoomTimelineView.swift; sourceTree = ""; }; + 18DF7C3F27E4670600291672 /* SeparatorRoomTimelineView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeparatorRoomTimelineView.swift; sourceTree = ""; }; + 18DF7C4027E4670600291672 /* TextRoomTimelineView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineView.swift; sourceTree = ""; }; + 18DF7C4B27E4672C00291672 /* EventBasedTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBasedTimelineItemProtocol.swift; sourceTree = ""; }; + 18DF7C4D27E4673E00291672 /* DecorationTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecorationTimelineItemProtocol.swift; sourceTree = ""; }; + 18DF7C4F27E46A7A00291672 /* EventBasedTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBasedTimelineView.swift; sourceTree = ""; }; + 18DF7C5227E4754500291672 /* MemberDetailsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberDetailsProvider.swift; sourceTree = ""; }; 18F2BA7727D25B4000DD1988 /* RoomTimelineProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomTimelineProvider.swift; sourceTree = ""; }; 18F2BA7927D25B4000DD1988 /* AuthenticationCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationCoordinator.swift; sourceTree = ""; }; 18F2BA7A27D25B4000DD1988 /* UserSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserSession.swift; sourceTree = ""; }; @@ -199,12 +215,6 @@ 18F2BB1927D25BE800DD1988 /* RoomTimelineController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineController.swift; sourceTree = ""; }; 18F2BB2727D2647A00DD1988 /* MockRoomTimelineController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomTimelineController.swift; sourceTree = ""; }; 18F2BB2927D2648900DD1988 /* RoomTimelineControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineControllerProtocol.swift; sourceTree = ""; }; - 18F9889727DB7473002F48B4 /* ImageRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRoomTimelineItem.swift; sourceTree = ""; }; - 18F9889927DB747B002F48B4 /* TextRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineItem.swift; sourceTree = ""; }; - 18F9889B27DB7491002F48B4 /* SeparatorRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorRoomTimelineItem.swift; sourceTree = ""; }; - 18F9889D27DB752B002F48B4 /* ImageRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRoomTimelineView.swift; sourceTree = ""; }; - 18F9889F27DB7532002F48B4 /* TextRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineView.swift; sourceTree = ""; }; - 18F988A127DB753B002F48B4 /* SeparatorRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorRoomTimelineView.swift; sourceTree = ""; }; 18FE279627C7B85300016375 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ @@ -309,23 +319,6 @@ path = "Supporting Files"; sourceTree = ""; }; - 18A318D927DA42C9000867CD /* TimelineItems */ = { - isa = PBXGroup; - children = ( - 18DF7C2927E23E3A00291672 /* RoomTimelineItemProtocol.swift */, - 18C5745727E1EB6E00D70937 /* RoomTimelineItemFactory.swift */, - 18DF7C2B27E23EC000291672 /* RoomTimelineViewFactory.swift */, - 18A318DB27DA42C9000867CD /* RoomTimelineViewProvider.swift */, - 18F9889727DB7473002F48B4 /* ImageRoomTimelineItem.swift */, - 18F9889D27DB752B002F48B4 /* ImageRoomTimelineView.swift */, - 18F9889927DB747B002F48B4 /* TextRoomTimelineItem.swift */, - 18F9889F27DB7532002F48B4 /* TextRoomTimelineView.swift */, - 18F9889B27DB7491002F48B4 /* SeparatorRoomTimelineItem.swift */, - 18F988A127DB753B002F48B4 /* SeparatorRoomTimelineView.swift */, - ); - path = TimelineItems; - sourceTree = ""; - }; 18C5744427E11F1900D70937 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -336,6 +329,7 @@ 18C5744727E1D84000D70937 /* Room */ = { isa = PBXGroup; children = ( + 18DF7C5127E4753A00291672 /* Members */, 18C5744927E1D84000D70937 /* Messages */, 18C5744827E1D84000D70937 /* RoomProxyProtocol.swift */, 18C5744A27E1D84000D70937 /* RoomProxy.swift */, @@ -365,6 +359,51 @@ path = Media; sourceTree = ""; }; + 18DF7C3427E4670600291672 /* TimelineItems */ = { + isa = PBXGroup; + children = ( + 18DF7C3627E4670600291672 /* RoomTimelineItemFactory.swift */, + 18DF7C3827E4670600291672 /* RoomTimelineViewFactory.swift */, + 18DF7C3527E4670600291672 /* RoomTimelineViewProvider.swift */, + 18DF7C3727E4670600291672 /* RoomTimelineItemProtocol.swift */, + 18DF7C4B27E4672C00291672 /* EventBasedTimelineItemProtocol.swift */, + 18DF7C4D27E4673E00291672 /* DecorationTimelineItemProtocol.swift */, + 18DF7C3927E4670600291672 /* Items */, + 18DF7C3D27E4670600291672 /* Views */, + ); + path = TimelineItems; + sourceTree = ""; + }; + 18DF7C3927E4670600291672 /* Items */ = { + isa = PBXGroup; + children = ( + 18DF7C3A27E4670600291672 /* SeparatorRoomTimelineItem.swift */, + 18DF7C3B27E4670600291672 /* ImageRoomTimelineItem.swift */, + 18DF7C3C27E4670600291672 /* TextRoomTimelineItem.swift */, + ); + path = Items; + sourceTree = ""; + }; + 18DF7C3D27E4670600291672 /* Views */ = { + isa = PBXGroup; + children = ( + 183E023127E4A3CF00903BED /* PlaceholderAvatarImage.swift */, + 18DF7C4F27E46A7A00291672 /* EventBasedTimelineView.swift */, + 18DF7C4027E4670600291672 /* TextRoomTimelineView.swift */, + 18DF7C3E27E4670600291672 /* ImageRoomTimelineView.swift */, + 18DF7C3F27E4670600291672 /* SeparatorRoomTimelineView.swift */, + ); + path = Views; + sourceTree = ""; + }; + 18DF7C5127E4753A00291672 /* Members */ = { + isa = PBXGroup; + children = ( + 18DF7C5227E4754500291672 /* MemberDetailsProvider.swift */, + ); + path = Members; + sourceTree = ""; + }; 18F2BA7227D25B4000DD1988 /* Services */ = { isa = PBXGroup; children = ( @@ -383,7 +422,7 @@ 18F2BB1927D25BE800DD1988 /* RoomTimelineController.swift */, 18F2BB2727D2647A00DD1988 /* MockRoomTimelineController.swift */, 18F2BA7727D25B4000DD1988 /* RoomTimelineProvider.swift */, - 18A318D927DA42C9000867CD /* TimelineItems */, + 18DF7C3427E4670600291672 /* TimelineItems */, ); path = Timeline; sourceTree = ""; @@ -814,24 +853,28 @@ files = ( 18F2BADE27D25B4000DD1988 /* KeychainControllerProtocol.swift in Sources */, 18C5745027E1D87800D70937 /* RoomMessageProtocol.swift in Sources */, + 18DF7C4C27E4672C00291672 /* EventBasedTimelineItemProtocol.swift in Sources */, 18F2BAED27D25B4000DD1988 /* FullscreenLoadingActivityPresenter.swift in Sources */, + 18DF7C4127E4670600291672 /* RoomTimelineViewProvider.swift in Sources */, 18F2BB0F27D25B4000DD1988 /* RoomScreen.swift in Sources */, 18F2BAFF27D25B4000DD1988 /* HomeScreenModels.swift in Sources */, - 18F9889C27DB7491002F48B4 /* SeparatorRoomTimelineItem.swift in Sources */, + 18DF7C4E27E4673E00291672 /* DecorationTimelineItemProtocol.swift in Sources */, 18F2BB1527D25B4000DD1988 /* LoginScreenViewModelProtocol.swift in Sources */, 18F2BAEB27D25B4000DD1988 /* LabelledActivityIndicatorView.swift in Sources */, 18F2BAE427D25B4000DD1988 /* Presentable.swift in Sources */, - 18DF7C2A27E23E3A00291672 /* RoomTimelineItemProtocol.swift in Sources */, + 18DF7C4427E4670600291672 /* RoomTimelineViewFactory.swift in Sources */, + 183E023227E4A3CF00903BED /* PlaceholderAvatarImage.swift in Sources */, 18F2BAF927D25B4000DD1988 /* SplashViewController.swift in Sources */, 18F2BAE327D25B4000DD1988 /* RootRouter.swift in Sources */, 18F2BAE527D25B4000DD1988 /* NavigationModule.swift in Sources */, 18F2BB1227D25B4000DD1988 /* LoginScreenViewModel.swift in Sources */, 18F2BAE727D25B4000DD1988 /* RoundedToastView.swift in Sources */, 18F2BAF227D25B4000DD1988 /* WeakDictionaryKeyReference.swift in Sources */, - 18F988A027DB7532002F48B4 /* TextRoomTimelineView.swift in Sources */, + 18DF7C4727E4670600291672 /* TextRoomTimelineItem.swift in Sources */, 18F2BAE027D25B4000DD1988 /* NavigationRouter.swift in Sources */, 18F2BAF627D25B4000DD1988 /* Coordinator.swift in Sources */, 18F2BAEA27D25B4000DD1988 /* ActivityCenter.swift in Sources */, + 18DF7C4827E4670600291672 /* ImageRoomTimelineView.swift in Sources */, 18F2BB1A27D25BE800DD1988 /* RoomTimelineController.swift in Sources */, 18F2BAF327D25B4000DD1988 /* WeakDictionaryReference.swift in Sources */, 18F2BB2A27D2648900DD1988 /* RoomTimelineControllerProtocol.swift in Sources */, @@ -842,46 +885,47 @@ 18F2BB1127D25B4000DD1988 /* RoomScreenModels.swift in Sources */, 18F2BADB27D25B4000DD1988 /* AuthenticationCoordinator.swift in Sources */, 1850256F27B6A135002E6B18 /* AppDelegate.swift in Sources */, + 18DF7C4927E4670600291672 /* SeparatorRoomTimelineView.swift in Sources */, 18F2BAE627D25B4000DD1988 /* NavigationRouterType.swift in Sources */, 18F2BAE927D25B4000DD1988 /* ActivityPresentable.swift in Sources */, + 18DF7C4327E4670600291672 /* RoomTimelineItemProtocol.swift in Sources */, 18F2BAF827D25B4000DD1988 /* BindableState.swift in Sources */, 18F2BB1827D25B4000DD1988 /* LoginScreen.swift in Sources */, + 18DF7C5327E4754500291672 /* MemberDetailsProvider.swift in Sources */, 18F2BAE227D25B4000DD1988 /* NavigationRouterStoreProtocol.swift in Sources */, 18F2BB1727D25B4000DD1988 /* LoginScreenCoordinator.swift in Sources */, 18F2BAF527D25B4000DD1988 /* WeakKeyDictionary.swift in Sources */, 18F2BADF27D25B4000DD1988 /* NavigationRouterStore.swift in Sources */, 18F2BAFE27D25B4000DD1988 /* HomeScreenViewModelProtocol.swift in Sources */, 18DF7C3327E3608800291672 /* MockMediaProvider.swift in Sources */, + 18DF7C4627E4670600291672 /* ImageRoomTimelineItem.swift in Sources */, 18F2BAE827D25B4000DD1988 /* RectangleToastView.swift in Sources */, 18F2BB1627D25B4000DD1988 /* LoginScreenModels.swift in Sources */, - 18F9889E27DB752B002F48B4 /* ImageRoomTimelineView.swift in Sources */, 18F2BADA27D25B4000DD1988 /* RoomTimelineProvider.swift in Sources */, 18DF7C2F27E264FC00291672 /* MediaProvider.swift in Sources */, 18C5744D27E1D84000D70937 /* RoomProxy.swift in Sources */, - 18F9889827DB7473002F48B4 /* ImageRoomTimelineItem.swift in Sources */, 18F2BB0027D25B4000DD1988 /* HomeScreen.swift in Sources */, 18F2BB2827D2647A00DD1988 /* MockRoomTimelineController.swift in Sources */, 18DF7C3127E3608100291672 /* MediaProviderProtocol.swift in Sources */, 18F2BB0127D25B4000DD1988 /* HomeScreenViewModel.swift in Sources */, 18F2BAF027D25B4000DD1988 /* ActivityDismissal.swift in Sources */, 18F2BADD27D25B4000DD1988 /* KeychainController.swift in Sources */, - 18A318DD27DA42C9000867CD /* RoomTimelineViewProvider.swift in Sources */, 18F2BAFB27D25B4000DD1988 /* HomeScreenCoordinator.swift in Sources */, 18F2BB0C27D25B4000DD1988 /* RoomScreenCoordinator.swift in Sources */, + 18DF7C5027E46A7A00291672 /* EventBasedTimelineView.swift in Sources */, + 18DF7C4A27E4670600291672 /* TextRoomTimelineView.swift in Sources */, + 18DF7C4527E4670600291672 /* SeparatorRoomTimelineItem.swift in Sources */, + 18DF7C4227E4670600291672 /* RoomTimelineItemFactory.swift in Sources */, 18F2BB0E27D25B4000DD1988 /* RoomScreenViewModelProtocol.swift in Sources */, 18F2BB0D27D25B4000DD1988 /* RoomScreenViewModel.swift in Sources */, - 18F988A227DB753B002F48B4 /* SeparatorRoomTimelineView.swift in Sources */, 18C5745427E1D88E00D70937 /* TextRoomMessage.swift in Sources */, 18C5745227E1D88600D70937 /* ImageRoomMessage.swift in Sources */, 18F2BAE127D25B4000DD1988 /* RootRouterType.swift in Sources */, 1850256C27B6A135002E6B18 /* AppCoordinator.swift in Sources */, - 18F9889A27DB747B002F48B4 /* TextRoomTimelineItem.swift in Sources */, 18C5744C27E1D84000D70937 /* RoomProxyProtocol.swift in Sources */, 18C5744E27E1D84000D70937 /* MockRoomProxy.swift in Sources */, 18F2BADC27D25B4000DD1988 /* UserSession.swift in Sources */, 18F2BAEF27D25B4000DD1988 /* ActivityRequest.swift in Sources */, - 18DF7C2C27E23EC000291672 /* RoomTimelineViewFactory.swift in Sources */, - 18C5745827E1EB6E00D70937 /* RoomTimelineItemFactory.swift in Sources */, 18F2BAEE27D25B4000DD1988 /* Activity.swift in Sources */, 18F2BAEC27D25B4000DD1988 /* ToastActivityPresenter.swift in Sources */, ); diff --git a/ElementX/Sources/AppCoordinator.swift b/ElementX/Sources/AppCoordinator.swift index 4d9357c1e..a76ec177e 100644 --- a/ElementX/Sources/AppCoordinator.swift +++ b/ElementX/Sources/AppCoordinator.swift @@ -113,7 +113,18 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator { return } - let parameters = RoomScreenCoordinatorParameters(roomProxy: roomProxy, mediaProvider: userSession.mediaProvider) + let memberDetailsProvider = MemberDetailsProvider(roomProxy: roomProxy) + + let timelineItemFactory = RoomTimelineItemFactory(mediaProvider: userSession.mediaProvider, + memberDetailsProvider: memberDetailsProvider) + + let timelineController = RoomTimelineController(timelineProvider: RoomTimelineProvider(roomProxy: roomProxy), + timelineItemFactory: timelineItemFactory, + mediaProvider: userSession.mediaProvider, + memberDetailsProvider: memberDetailsProvider) + + let parameters = RoomScreenCoordinatorParameters(timelineController: timelineController, + roomName: roomProxy.name) let coordinator = RoomScreenCoordinator(parameters: parameters) self.add(childCoordinator: coordinator) diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift index cdb14256d..5074cb2b8 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift @@ -17,8 +17,8 @@ import SwiftUI struct RoomScreenCoordinatorParameters { - let roomProxy: RoomProxyProtocol - let mediaProvider: MediaProviderProtocol + let timelineController: RoomTimelineControllerProtocol + let roomName: String? } final class RoomScreenCoordinator: Coordinator, Presentable { @@ -42,14 +42,10 @@ final class RoomScreenCoordinator: Coordinator, Presentable { init(parameters: RoomScreenCoordinatorParameters) { self.parameters = parameters - let timelineProvider = RoomTimelineProvider(roomProxy: parameters.roomProxy) - let timelineController = RoomTimelineController(timelineProvider: timelineProvider, - timelineItemFactory: RoomTimelineItemFactory(mediaProvider: parameters.mediaProvider), - mediaProvider: parameters.mediaProvider) + let viewModel = RoomScreenViewModel(timelineController: parameters.timelineController, + timelineViewFactory: RoomTimelineViewFactory(), + roomName: parameters.roomName) - let viewModel = RoomScreenViewModel(roomProxy: parameters.roomProxy, - timelineController: timelineController, - timelineViewFactory: RoomTimelineViewFactory()) let view = RoomScreen(context: viewModel.context) roomScreenViewModel = viewModel roomScreenHostingController = UIHostingController(rootView: view) diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index e2a168146..62ba1ff44 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -18,8 +18,8 @@ import SwiftUI @available(iOS 14, *) typealias RoomScreenViewModelType = StateStoreViewModel + Never, + RoomScreenViewAction> @available(iOS 14, *) class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol { @@ -27,22 +27,19 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol static let backPaginationPageSize: UInt = 30 } - private let roomProxy: RoomProxyProtocol private let timelineController: RoomTimelineControllerProtocol private let timelineViewFactory: RoomTimelineViewFactory // MARK: - Setup - - init(roomProxy: RoomProxyProtocol, - timelineController: RoomTimelineControllerProtocol, - timelineViewFactory: RoomTimelineViewFactory) { - self.roomProxy = roomProxy + + init(timelineController: RoomTimelineControllerProtocol, + timelineViewFactory: RoomTimelineViewFactory, + roomName: String?) { self.timelineController = timelineController self.timelineViewFactory = timelineViewFactory - super.init(initialViewState: RoomScreenViewState()) + super.init(initialViewState: RoomScreenViewState(roomTitle: roomName ?? "💥")) - state.roomTitle = roomProxy.name ?? "" buildTimelineViews() timelineController.callbacks.sink { [weak self] callback in diff --git a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift index dc26cb76f..7778dce61 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift @@ -240,9 +240,9 @@ private class TableViewObserver: NSObject, UITableViewDelegate { struct RoomScreen_Previews: PreviewProvider { static var previews: some View { - let viewModel = RoomScreenViewModel(roomProxy: MockRoomProxy(displayName: "Test"), - timelineController: MockRoomTimelineController(), - timelineViewFactory: RoomTimelineViewFactory()) + let viewModel = RoomScreenViewModel(timelineController: MockRoomTimelineController(), + timelineViewFactory: RoomTimelineViewFactory(), + roomName: "Preview room") RoomScreen(context: viewModel.context) } diff --git a/ElementX/Sources/Services/Media/MediaProvider.swift b/ElementX/Sources/Services/Media/MediaProvider.swift index 7297a1b2a..9461823d5 100644 --- a/ElementX/Sources/Services/Media/MediaProvider.swift +++ b/ElementX/Sources/Services/Media/MediaProvider.swift @@ -13,16 +13,18 @@ import Kingfisher struct MediaProvider: MediaProviderProtocol { private let client: Client private let imageCache: Kingfisher.ImageCache + private let processingQueue: DispatchQueue init(client: Client, imageCache: Kingfisher.ImageCache) { self.client = client self.imageCache = imageCache + self.processingQueue = DispatchQueue(label: "MediaProviderProcessingQueue") } func loadCurrentUserAvatar(_ completion: @escaping (Result) -> Void) { - DispatchQueue.global(qos: .background).async { + processingQueue.async { do { - let imageData = try self.client.avatar() + let imageData = try client.avatar() DispatchQueue.main.async { completion(.success(UIImage(data: Data(bytes: imageData, count: imageData.count)))) } @@ -35,21 +37,25 @@ struct MediaProvider: MediaProviderProtocol { } } - func hasImageCachedForURL(_ url: String) -> Bool { - self.imageCache.imageCachedType(forKey: url) == .memory + func imageForURL(_ url: String?) -> UIImage? { + guard let url = url else { + return nil + } + + return imageCache.retrieveImageInMemoryCache(forKey: url, options: nil) } func loadImageFromURL(_ url: String, _ completion: @escaping (Result) -> Void) { - self.imageCache.retrieveImage(forKey: url) { result in + imageCache.retrieveImage(forKey: url) { result in if case let .success(cacheResult) = result, let image = cacheResult.image { completion(.success(image)) } } - DispatchQueue.global(qos: .background).async { + processingQueue.async { do { - let imageData = try self.client.loadImage(url: url) + let imageData = try client.loadImage(url: url) guard let image = UIImage(data: Data(bytes: imageData, count: imageData.count)) else { MXLog.error("Invalid image data") @@ -59,7 +65,7 @@ struct MediaProvider: MediaProviderProtocol { return } - self.imageCache.store(image, forKey: url) + imageCache.store(image, forKey: url) DispatchQueue.main.async { completion(.success(image)) diff --git a/ElementX/Sources/Services/Media/MediaProviderProtocol.swift b/ElementX/Sources/Services/Media/MediaProviderProtocol.swift index 44e4fbe7f..9806dfec3 100644 --- a/ElementX/Sources/Services/Media/MediaProviderProtocol.swift +++ b/ElementX/Sources/Services/Media/MediaProviderProtocol.swift @@ -16,6 +16,8 @@ enum MediaProviderError: Error { protocol MediaProviderProtocol { func loadCurrentUserAvatar(_ completion: @escaping (Result) -> Void) - func hasImageCachedForURL(_ url: String) -> Bool + + func imageForURL(_ url: String?) -> UIImage? + func loadImageFromURL(_ url: String, _ completion: @escaping (Result) -> Void) } diff --git a/ElementX/Sources/Services/Media/MockMediaProvider.swift b/ElementX/Sources/Services/Media/MockMediaProvider.swift index 208283287..63efb2eca 100644 --- a/ElementX/Sources/Services/Media/MockMediaProvider.swift +++ b/ElementX/Sources/Services/Media/MockMediaProvider.swift @@ -15,8 +15,8 @@ struct MockMediaProvider: MediaProviderProtocol { } - func hasImageCachedForURL(_ url: String) -> Bool { - true + func imageForURL(_ url: String?) -> UIImage? { + return nil } func loadImageFromURL(_ url: String, _ completion: @escaping (Result) -> Void) { diff --git a/ElementX/Sources/Services/Room/Members/MemberDetailsProvider.swift b/ElementX/Sources/Services/Room/Members/MemberDetailsProvider.swift new file mode 100644 index 000000000..b00624ad1 --- /dev/null +++ b/ElementX/Sources/Services/Room/Members/MemberDetailsProvider.swift @@ -0,0 +1,58 @@ +// +// MemberDetailsProvider.swift +// ElementX +// +// Created by Stefan Ceriu on 18/03/2022. +// Copyright © 2022 Element. All rights reserved. +// + +import Foundation + +enum MemberDetailsProviderError: Error { + case invalidRoomProxy + case failedRetrievingUserAvatarURL +} + +class MemberDetailsProvider { + private let roomProxy: RoomProxyProtocol? + private let processingQueue = DispatchQueue(label: "MemberDetailsProviderProcessingQueue") + private var memberAvatars = [String: String]() + + init(roomProxy: RoomProxyProtocol) { + self.roomProxy = roomProxy + } + + func avatarURLForUserId(_ userId: String) -> String? { + self.memberAvatars[userId] + } + + func avatarURLForUserId(_ userId: String, completion: @escaping (Result) -> Void) { + guard let roomProxy = roomProxy else { + return + } + + if let avatarURL = avatarURLForUserId(userId) { + completion(.success(avatarURL)) + } + + processingQueue.async { + roomProxy.avatarURLForUserId(userId, completion: { [weak self] result in + guard let self = self else { + return + } + + switch result { + case .success(let avatarURL): + DispatchQueue.main.async { + self.memberAvatars[userId] = avatarURL + completion(.success(avatarURL)) + } + case .failure: + DispatchQueue.main.async { + completion(.failure(.failedRetrievingUserAvatarURL)) + } + } + }) + } + } +} diff --git a/ElementX/Sources/Services/Room/RoomProxy.swift b/ElementX/Sources/Services/Room/RoomProxy.swift index d286444ad..36cb92008 100644 --- a/ElementX/Sources/Services/Room/RoomProxy.swift +++ b/ElementX/Sources/Services/Room/RoomProxy.swift @@ -91,7 +91,7 @@ class RoomProxy: RoomProxyProtocol, Equatable { } func avatarURLForUserId(_ userId: String, completion: @escaping (Result) -> Void) { - DispatchQueue.global(qos: .background).async { + processingQueue.async { do { let avatarURL = try self.room.memberAvatarUrl(userId: userId) @@ -107,7 +107,7 @@ class RoomProxy: RoomProxyProtocol, Equatable { } func loadDisplayName(_ completion: @escaping (Result) -> Void) { - DispatchQueue.global(qos: .background).async { + processingQueue.async { do { let displayName = try self.room.displayName() @@ -123,7 +123,6 @@ class RoomProxy: RoomProxyProtocol, Equatable { } func paginateBackwards(count: UInt, callback: ((Result<[RoomMessageProtocol], RoomProxyError>) -> Void)?) { - MXLog.debug("Started backpaginating") processingQueue.async { guard let backwardStream = self.backwardStream else { DispatchQueue.main.async { @@ -136,8 +135,6 @@ class RoomProxy: RoomProxyProtocol, Equatable { self.messageFactory.buildRoomMessageFrom(message) } - MXLog.debug("Finished backpaginating") - DispatchQueue.main.async { callback?(.success(messages)) if self.lastMessage == nil { diff --git a/ElementX/Sources/Services/Timeline/RoomTimelineController.swift b/ElementX/Sources/Services/Timeline/RoomTimelineController.swift index b41120a8c..f51c050e0 100644 --- a/ElementX/Sources/Services/Timeline/RoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/RoomTimelineController.swift @@ -13,6 +13,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol { private let timelineProvider: RoomTimelineProvider private let timelineItemFactory: RoomTimelineItemFactory private let mediaProvider: MediaProviderProtocol + private let memberDetailsProvider: MemberDetailsProvider private var cancellables = Set() @@ -22,10 +23,12 @@ class RoomTimelineController: RoomTimelineControllerProtocol { init(timelineProvider: RoomTimelineProvider, timelineItemFactory: RoomTimelineItemFactory, - mediaProvider: MediaProviderProtocol) { + mediaProvider: MediaProviderProtocol, + memberDetailsProvider: MemberDetailsProvider) { self.timelineProvider = timelineProvider self.timelineItemFactory = timelineItemFactory self.mediaProvider = mediaProvider + self.memberDetailsProvider = memberDetailsProvider self.timelineProvider.callbacks.sink { [weak self] callback in guard let self = self else { return } @@ -54,33 +57,13 @@ class RoomTimelineController: RoomTimelineControllerProtocol { return } - loadAvatarIfNeededForTimelineItem(timelineItem) - - switch timelineItem { - case var item as ImageRoomTimelineItem: - if item.image != nil { - return - } - - guard let url = item.url else { - return - } - - mediaProvider.loadImageFromURL(url) { [weak self] result in - guard let self = self else { - return - } + if let item = timelineItem as? EventBasedTimelineItemProtocol { + loadAvatarForTimelineItem(item) + } - if case let .success(image) = result { - guard let index = self.timelineItems.firstIndex(where: { $0.id == itemId }) else { - return - } - - item.image = image - self.timelineItems[index] = item - self.callbacks.send(.updatedTimelineItem(itemId)) - } - } + switch timelineItem { + case let item as ImageRoomTimelineItem: + loadImageForTimelineItem(item) default: break } @@ -126,42 +109,56 @@ class RoomTimelineController: RoomTimelineControllerProtocol { return Calendar.current.isDate(lhs.originServerTs, inSameDayAs: rhs.originServerTs) } - private func loadAvatarIfNeededForTimelineItem(_ timelineItem: RoomTimelineItemProtocol) { - switch timelineItem { - case var item as BaseRoomTimelineItemProtocol: - if item.shouldShowSenderDetails == false { - break + private func loadImageForTimelineItem(_ timelineItem: ImageRoomTimelineItem) { + var item = timelineItem + + if item.image != nil { + return + } + + guard let url = item.url else { + return + } + + mediaProvider.loadImageFromURL(url) { [weak self] result in + guard let self = self else { + return } - timelineProvider.avatarURLForUserId(item.sender) { [weak self] result in - guard let self = self else { + if case let .success(image) = result { + guard let index = self.timelineItems.firstIndex(where: { $0.id == timelineItem.id }) else { return } - switch result { - case .success(let userAvatarURL): - guard let avatarURL = userAvatarURL else { - return - } - - self.mediaProvider.loadImageFromURL(avatarURL) { result in - if case let .success(image) = result { - guard let index = self.timelineItems.firstIndex(where: { $0.id == timelineItem.id }) else { - return - } - - item.senderAvatar = image - self.timelineItems[index] = item - self.callbacks.send(.updatedTimelineItem(timelineItem.id)) + item.image = image + self.timelineItems[index] = item + self.callbacks.send(.updatedTimelineItem(timelineItem.id)) + } + } + } + + private func loadAvatarForTimelineItem(_ timelineItem: EventBasedTimelineItemProtocol) { + var item = timelineItem + + if item.shouldShowSenderDetails == false { + return + } + + memberDetailsProvider.avatarURLForUserId(timelineItem.sender) { result in + if case let .success(avatarURL) = result, + let avatarURL = avatarURL { + self.mediaProvider.loadImageFromURL(avatarURL) { result in + if case let .success(image) = result { + guard let index = self.timelineItems.firstIndex(where: { $0.id == timelineItem.id }) else { + return } + + item.senderAvatar = image + self.timelineItems[index] = item + self.callbacks.send(.updatedTimelineItem(timelineItem.id)) } - case .failure: - MXLog.error("Failed retrieving user avatar") } } - - default: - break } } } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/DecorationTimelineItemProtocol.swift b/ElementX/Sources/Services/Timeline/TimelineItems/DecorationTimelineItemProtocol.swift new file mode 100644 index 000000000..ca291f50d --- /dev/null +++ b/ElementX/Sources/Services/Timeline/TimelineItems/DecorationTimelineItemProtocol.swift @@ -0,0 +1,13 @@ +// +// DecorationTimelineItemProtocol.swift +// ElementX +// +// Created by Stefan Ceriu on 18/03/2022. +// Copyright © 2022 Element. All rights reserved. +// + +import Foundation + +protocol DecorationTimelineItemProtocol: RoomTimelineItemProtocol { + +} diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/EventBasedTimelineItemProtocol.swift b/ElementX/Sources/Services/Timeline/TimelineItems/EventBasedTimelineItemProtocol.swift new file mode 100644 index 000000000..971819b89 --- /dev/null +++ b/ElementX/Sources/Services/Timeline/TimelineItems/EventBasedTimelineItemProtocol.swift @@ -0,0 +1,19 @@ +// +// EventBasedTimelineItemProtocol.swift +// ElementX +// +// Created by Stefan Ceriu on 18/03/2022. +// Copyright © 2022 Element. All rights reserved. +// + +import Foundation +import UIKit + +protocol EventBasedTimelineItemProtocol: RoomTimelineItemProtocol { + var text: String { get } + var timestamp: String { get } + var shouldShowSenderDetails: Bool { get } + + var sender: String { get } + var senderAvatar: UIImage? { get set } +} diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/ImageRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/ImageRoomTimelineItem.swift similarity index 81% rename from ElementX/Sources/Services/Timeline/TimelineItems/ImageRoomTimelineItem.swift rename to ElementX/Sources/Services/Timeline/TimelineItems/Items/ImageRoomTimelineItem.swift index 46e8ca44f..9511e6bd2 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/ImageRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/ImageRoomTimelineItem.swift @@ -9,7 +9,7 @@ import Foundation import UIKit -struct ImageRoomTimelineItem: BaseRoomTimelineItemProtocol, Identifiable, Equatable { +struct ImageRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equatable { let id: String let text: String let timestamp: String diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/SeparatorRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/SeparatorRoomTimelineItem.swift similarity index 70% rename from ElementX/Sources/Services/Timeline/TimelineItems/SeparatorRoomTimelineItem.swift rename to ElementX/Sources/Services/Timeline/TimelineItems/Items/SeparatorRoomTimelineItem.swift index 26debc00f..f5b2b39e0 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/SeparatorRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/SeparatorRoomTimelineItem.swift @@ -8,7 +8,7 @@ import Foundation -struct SeparatorRoomTimelineItem: RoomTimelineItemProtocol, Identifiable, Equatable { +struct SeparatorRoomTimelineItem: DecorationTimelineItemProtocol, Identifiable, Equatable { let id: String let text: String } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/TextRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/TextRoomTimelineItem.swift similarity index 79% rename from ElementX/Sources/Services/Timeline/TimelineItems/TextRoomTimelineItem.swift rename to ElementX/Sources/Services/Timeline/TimelineItems/Items/TextRoomTimelineItem.swift index 052930ca5..367675fc0 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/TextRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/TextRoomTimelineItem.swift @@ -9,7 +9,7 @@ import Foundation import UIKit -struct TextRoomTimelineItem: BaseRoomTimelineItemProtocol, Identifiable, Equatable { +struct TextRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equatable { let id: String let text: String let timestamp: String diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift index d011ec3f8..590910231 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift @@ -11,39 +11,36 @@ import UIKit struct RoomTimelineItemFactory { private let mediaProvider: MediaProviderProtocol + private let memberDetailsProvider: MemberDetailsProvider - init(mediaProvider: MediaProviderProtocol) { + init(mediaProvider: MediaProviderProtocol, + memberDetailsProvider: MemberDetailsProvider) { self.mediaProvider = mediaProvider + self.memberDetailsProvider = memberDetailsProvider } func buildTimelineItemFor(_ roomMessage: RoomMessageProtocol, showSenderDetails: Bool) -> RoomTimelineItemProtocol { + + let avatarURL = memberDetailsProvider.avatarURLForUserId(roomMessage.sender) + let avatarImage = mediaProvider.imageForURL(avatarURL) + switch roomMessage { case let message as TextRoomMessage: return TextRoomTimelineItem(id: message.id, text: message.content, timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened), shouldShowSenderDetails: showSenderDetails, - sender: message.sender) + sender: message.sender, + senderAvatar: avatarImage) case let message as ImageRoomMessage: - var image: UIImage? - - if let url = message.url { - if mediaProvider.hasImageCachedForURL(url) { - mediaProvider.loadImageFromURL(url, { result in - if case let .success(cachedImage) = result { - image = cachedImage - } - }) - } - } - return ImageRoomTimelineItem(id: message.id, text: message.content, timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened), shouldShowSenderDetails: showSenderDetails, sender: message.sender, + senderAvatar: avatarImage, url: message.url, - image: image) + image: mediaProvider.imageForURL(message.url)) default: fatalError("Unknown room message.") } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemProtocol.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemProtocol.swift index 1d663d9ed..17e66b5a6 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemProtocol.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemProtocol.swift @@ -12,12 +12,3 @@ import UIKit protocol RoomTimelineItemProtocol { var id: String { get } } - -protocol BaseRoomTimelineItemProtocol: RoomTimelineItemProtocol { - var text: String { get } - var timestamp: String { get } - var shouldShowSenderDetails: Bool { get } - - var sender: String { get } - var senderAvatar: UIImage? { get set } -} diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Views/EventBasedTimelineView.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Views/EventBasedTimelineView.swift new file mode 100644 index 000000000..4d405b030 --- /dev/null +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Views/EventBasedTimelineView.swift @@ -0,0 +1,44 @@ +// +// EventBasedTimelineView.swift +// ElementX +// +// Created by Stefan Ceriu on 18/03/2022. +// Copyright © 2022 Element. All rights reserved. +// + +import Foundation +import SwiftUI + +struct EventBasedTimelineView: View { + let timelineItem: EventBasedTimelineItemProtocol + + var body: some View { + if timelineItem.shouldShowSenderDetails { + HStack { + avatar + Text(timelineItem.sender) + .font(.footnote) + .bold() + Spacer() + Text(timelineItem.timestamp) + .font(.footnote) + } + Divider() + } + } + + @ViewBuilder private var avatar: some View { + ZStack(alignment: .center) { + if let avatar = timelineItem.senderAvatar { + Image(uiImage: avatar) + .resizable() + .scaledToFill() + .overlay(Circle().stroke(Color(.sRGB, red: 0.05, green: 0.74, blue: 0.55, opacity: 1.0))) + } else { + PlaceholderAvatarImage(firstCharacter: String(timelineItem.sender.prefix(2).suffix(1)).uppercased()) + } + } + .clipShape(Circle()) + .frame(width: 44.0, height: 44.0) + } +} diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/ImageRoomTimelineView.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Views/ImageRoomTimelineView.swift similarity index 93% rename from ElementX/Sources/Services/Timeline/TimelineItems/ImageRoomTimelineView.swift rename to ElementX/Sources/Services/Timeline/TimelineItems/Views/ImageRoomTimelineView.swift index 2f0ca15b4..8c776946f 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/ImageRoomTimelineView.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Views/ImageRoomTimelineView.swift @@ -14,11 +14,9 @@ struct ImageRoomTimelineView: View { var body: some View { if let image = timelineItem.image { - VStack { - HStack { - Text(timelineItem.text) - Spacer() - } + VStack(alignment: .leading) { + EventBasedTimelineView(timelineItem: timelineItem) + Text(timelineItem.text) Image(uiImage: image) .resizable() .scaledToFit() diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/SeparatorRoomTimelineView.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Views/SeparatorRoomTimelineView.swift similarity index 100% rename from ElementX/Sources/Services/Timeline/TimelineItems/SeparatorRoomTimelineView.swift rename to ElementX/Sources/Services/Timeline/TimelineItems/Views/SeparatorRoomTimelineView.swift diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/TextRoomTimelineView.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Views/TextRoomTimelineView.swift similarity index 60% rename from ElementX/Sources/Services/Timeline/TimelineItems/TextRoomTimelineView.swift rename to ElementX/Sources/Services/Timeline/TimelineItems/Views/TextRoomTimelineView.swift index d32a34ec7..3f6f072ad 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/TextRoomTimelineView.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Views/TextRoomTimelineView.swift @@ -14,40 +14,13 @@ struct TextRoomTimelineView: View { var body: some View { VStack(alignment: .leading) { - if timelineItem.shouldShowSenderDetails { - HStack { - avatar - Text(timelineItem.sender) - .font(.footnote) - .bold() - Spacer() - Text(timelineItem.timestamp) - .font(.footnote) - } - Divider() - } + EventBasedTimelineView(timelineItem: timelineItem) Text(timelineItem.text) + .fixedSize(horizontal: false, vertical: true) } .id(timelineItem.id) } - - @ViewBuilder var avatar: some View { - ZStack(alignment: .center) { - Circle() - .fill(Color(.sRGB, red: 0.05, green: 0.74, blue: 0.55, opacity: 1.0)) - if let avatar = timelineItem.senderAvatar { - Image(uiImage: avatar) - .resizable() - .clipShape(Circle()) - } else { - Text(timelineItem.sender.prefix(2).suffix(1)) - .foregroundColor(.white) - .font(.title) - .bold() - } - } - .frame(width: 44.0, height: 44.0) - } + } struct TextRoomTimelineView_Previews: PreviewProvider {