diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 61e2aa0ea..d2ef9b2fc 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 51; objects = { /* Begin PBXBuildFile section */ @@ -134,6 +134,7 @@ 30CC1DB7CE357659C82AA115 /* MediaProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85EB16E7FE59A947CA441531 /* MediaProviderProtocol.swift */; }; 30CC4F796B27BE8B1DFDBF5A /* NSEUserSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAA2832D93EC7D2608703FB /* NSEUserSession.swift */; }; 3113065AABBC14CEAE6843FA /* UserSessionFlowCoordinatorStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8774CF614849664B5B3C2A1 /* UserSessionFlowCoordinatorStateMachine.swift */; }; + 3116693C5EB476E028990416 /* XCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74611A4182DCF5F4D42696EC /* XCTestCase.swift */; }; 329571083B132E4941131835 /* OnboardingBackgroundImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 686BCFA37AC6C67FF973CE67 /* OnboardingBackgroundImage.swift */; }; 339BC18777912E1989F2F17D /* Section.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584A61D9C459FAFEF038A7C0 /* Section.swift */; }; 339D847497C51F2B36E3666B /* FixedIconSizeLabelStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3518637393394901BF5BFAC3 /* FixedIconSizeLabelStyle.swift */; }; @@ -267,7 +268,6 @@ 659E5B766F76FDEC1BF393A4 /* RoomDetailsEditScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E413F4CBD7BF0588F394A9DD /* RoomDetailsEditScreenViewModel.swift */; }; 65EDA77363BEDC40CDE43B43 /* InvitesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42ADEA322D2089391E049535 /* InvitesScreen.swift */; }; 663E198678778F7426A9B27D /* Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FAFE1C2149E6AC8156ED2B /* Collection.swift */; }; - 664F77F02A57617A00FB9B24 /* XCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664F77EF2A57617A00FB9B24 /* XCTestCase.swift */; }; 6713835120D94BAA8ED7E3E5 /* MessageForwardingScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59846FA04E1DBBFDD8829C2A /* MessageForwardingScreenUITests.swift */; }; 67160204A8D362BB7D4AD259 /* Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 693E16574C6F7F9FA1015A8C /* Search.swift */; }; 6786C4B0936AC84D993B20BF /* NotificationSettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5F06F2F09B2EDD067DC2174 /* NotificationSettingsScreen.swift */; }; @@ -847,7 +847,7 @@ 127C8472672A5BA09EF1ACF8 /* CurrentValuePublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentValuePublisher.swift; sourceTree = ""; }; 12EDAFB64FA5F6812D54F39A /* MigrationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationScreenViewModel.swift; sourceTree = ""; }; 12F1E7F9C2BE8BB751037826 /* WaitlistScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenCoordinator.swift; sourceTree = ""; }; - 1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = IntegrationTests.xctestplan; sourceTree = ""; }; + 1304D9191300873EADA52D6E /* IntegrationTests.xctestplan */ = {isa = PBXFileReference; path = IntegrationTests.xctestplan; sourceTree = ""; }; 130ED565A078F7E0B59D9D25 /* UNTextInputNotificationResponse+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNTextInputNotificationResponse+Creator.swift"; sourceTree = ""; }; 13802897C7AFA360EA74C0B0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; }; 1423AB065857FA546444DB15 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = ""; }; @@ -985,7 +985,7 @@ 47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxyProtocol.swift; sourceTree = ""; }; 471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineView.swift; sourceTree = ""; }; 47873756E45B46683D97DC32 /* LegalInformationScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenModels.swift; sourceTree = ""; }; - 478BE8591BD13E908EF70C0C /* DesignKit */ = {isa = PBXFileReference; lastKnownFileType = folder; path = DesignKit; sourceTree = SOURCE_ROOT; }; + 478BE8591BD13E908EF70C0C /* DesignKit */ = {isa = PBXFileReference; lastKnownFileType = folder; name = DesignKit; path = DesignKit; sourceTree = SOURCE_ROOT; }; 4798B3B7A1E8AE3901CEE8C6 /* FramePreferenceKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FramePreferenceKey.swift; sourceTree = ""; }; 47EBB5D698CE9A25BB553A2D /* Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = ""; }; 47F29139BC2A804CE5E0757E /* MediaUploadPreviewScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenViewModel.swift; sourceTree = ""; }; @@ -1063,7 +1063,6 @@ 653610CB5F9776EAAAB98155 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fr; path = fr.lproj/Localizable.stringsdict; sourceTree = ""; }; 65AAD845E53B0C8B5E0812C2 /* UserDiscoveryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoveryService.swift; sourceTree = ""; }; 65C2B80DD0BF6F10BB5FA922 /* MockAuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAuthenticationServiceProxy.swift; sourceTree = ""; }; - 664F77EF2A57617A00FB9B24 /* XCTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTestCase.swift; sourceTree = ""; }; 6654859746B0BE9611459391 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = cs; path = cs.lproj/Localizable.stringsdict; sourceTree = ""; }; 667DD3A9D932D7D9EB380CAA /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = sk; path = sk.lproj/Localizable.stringsdict; sourceTree = ""; }; 669F35C505ACE1110589F875 /* MediaUploadingPreprocessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadingPreprocessor.swift; sourceTree = ""; }; @@ -1109,6 +1108,7 @@ 72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringBuilderProtocol.swift; sourceTree = ""; }; 7310D8DFE01AF45F0689C3AA /* Publisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Publisher.swift; sourceTree = ""; }; 745323FCF9AF21A117252C53 /* RoundedLabelItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedLabelItem.swift; sourceTree = ""; }; + 74611A4182DCF5F4D42696EC /* XCTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTestCase.swift; sourceTree = ""; }; 7475C5AE20BA896930907EA8 /* AudioRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRoomTimelineItemContent.swift; sourceTree = ""; }; 748AE77AC3B0A01223033B87 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 74DD0855F2F76D47E5555082 /* MediaUploadPreviewScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenCoordinator.swift; sourceTree = ""; }; @@ -1160,7 +1160,7 @@ 8D55702474F279D910D2D162 /* RoomStateEventStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomStateEventStringBuilder.swift; sourceTree = ""; }; 8D8169443E5AC5FF71BFB3DB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = ""; }; 8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyle.swift; sourceTree = ""; }; - 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UITests.xctestplan; sourceTree = ""; }; + 8E088F2A1B9EC529D3221931 /* UITests.xctestplan */ = {isa = PBXFileReference; path = UITests.xctestplan; sourceTree = ""; }; 8E1BBA73B611EDEEA6E20E05 /* InvitesScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenModels.swift; sourceTree = ""; }; 8EC57A32ABC80D774CC663DB /* SettingsScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenUITests.swift; sourceTree = ""; }; 8F21ED7205048668BEB44A38 /* AppActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppActivityView.swift; sourceTree = ""; }; @@ -1268,7 +1268,7 @@ B4CFE236419E830E8946639C /* Analytics+SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Analytics+SwiftUI.swift"; sourceTree = ""; }; B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableAvatarImage.swift; sourceTree = ""; }; B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineView.swift; sourceTree = ""; }; - B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = ConfettiScene.scn; sourceTree = ""; }; + B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */ = {isa = PBXFileReference; path = ConfettiScene.scn; sourceTree = ""; }; B6311F21F911E23BE4DF51B4 /* ReadMarkerRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadMarkerRoomTimelineView.swift; sourceTree = ""; }; B697816AF93DA06EC58C5D70 /* WaitlistScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenViewModelProtocol.swift; sourceTree = ""; }; B6E89E530A8E92EC44301CA1 /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = ""; }; @@ -1350,7 +1350,7 @@ CD6B0C4639E066915B5E6463 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = ""; }; CDB3227C7A74B734924942E9 /* RoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProvider.swift; sourceTree = ""; }; CEE0E6043EFCF6FD2A341861 /* TimelineReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReplyView.swift; sourceTree = ""; }; - CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = ""; }; + CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; path = UnitTests.xctestplan; sourceTree = ""; }; CF48AF076424DBC1615C74AD /* AuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProxy.swift; sourceTree = ""; }; D0140615D2232612C813FD6C /* EncryptedHistoryRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedHistoryRoomTimelineItem.swift; sourceTree = ""; }; D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoverySection.swift; sourceTree = ""; }; @@ -1420,7 +1420,7 @@ ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenViewModel.swift; sourceTree = ""; }; ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = ""; }; ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = ""; }; - ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = message.caf; sourceTree = ""; }; + ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; path = message.caf; sourceTree = ""; }; ED983D4DCA5AFA6E1ED96099 /* StateRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRoomTimelineView.swift; sourceTree = ""; }; EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelTests.swift; sourceTree = ""; }; EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItemContent.swift; sourceTree = ""; }; @@ -2377,7 +2377,7 @@ children = ( 60F18AECC9D38C2B6D85F99C /* Publisher.swift */, 818CBE6249ED6E8FC30E8366 /* ViewModelContext.swift */, - 664F77EF2A57617A00FB9B24 /* XCTestCase.swift */, + 74611A4182DCF5F4D42696EC /* XCTestCase.swift */, ); path = Extensions; sourceTree = ""; @@ -4067,7 +4067,6 @@ CC961529F9F1854BEC3272C9 /* LayoutMocks.swift in Sources */, 8AC256AF0EC54658321C9241 /* LegalInformationScreenViewModelTests.swift in Sources */, 0033481EE363E4914295F188 /* LocalizationTests.swift in Sources */, - 664F77F02A57617A00FB9B24 /* XCTestCase.swift in Sources */, 149D1942DC005D0485FB8D93 /* LoggingTests.swift in Sources */, 1E59B77A0B2CE83DCC1B203C /* LoginViewModelTests.swift in Sources */, 2E43A3D221BE9587BC19C3F1 /* MatrixEntityRegexTests.swift in Sources */, @@ -4121,6 +4120,7 @@ 99F8DA4CCC6772EE5FE68E24 /* ViewModelContext.swift in Sources */, FB9A1DD83EF641A75ABBCE69 /* WaitlistScreenViewModelTests.swift in Sources */, 7F02063FB3D1C3E5601471A1 /* WelcomeScreenScreenViewModelTests.swift in Sources */, + 3116693C5EB476E028990416 /* XCTestCase.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ElementX/Sources/Screens/InvitesScreen/InvitesScreenModels.swift b/ElementX/Sources/Screens/InvitesScreen/InvitesScreenModels.swift index b053f30e8..a8b064ef7 100644 --- a/ElementX/Sources/Screens/InvitesScreen/InvitesScreenModels.swift +++ b/ElementX/Sources/Screens/InvitesScreen/InvitesScreenModels.swift @@ -21,6 +21,10 @@ enum InvitesScreenViewModelAction { struct InvitesScreenViewState: BindableState { var invites: [InvitesScreenRoomDetails] = [] var bindings: InvitesScreenViewStateBindings = .init() + + // ideally we wanted to derive this state from `invites` being `Optional.none`. + // But we needed to make it a non optional array due to a strange crash in release builds (https://github.com/vector-im/element-x-ios/pull/1120) + var isLoading = true } struct InvitesScreenViewStateBindings { diff --git a/ElementX/Sources/Screens/InvitesScreen/InvitesScreenViewModel.swift b/ElementX/Sources/Screens/InvitesScreen/InvitesScreenViewModel.swift index 0928bcbc4..6b34d7bf6 100644 --- a/ElementX/Sources/Screens/InvitesScreen/InvitesScreenViewModel.swift +++ b/ElementX/Sources/Screens/InvitesScreen/InvitesScreenViewModel.swift @@ -28,7 +28,7 @@ class InvitesScreenViewModel: InvitesScreenViewModelType, InvitesScreenViewModel private let previouslySeenInvites: Set private let actionsSubject: PassthroughSubject = .init() @CancellableTask private var fetchInvitersTask: Task? - + var actions: AnyPublisher { actionsSubject.eraseToAnyPublisher() } @@ -75,62 +75,57 @@ class InvitesScreenViewModel: InvitesScreenViewModelType, InvitesScreenViewModel } inviteSummaryProvider.roomListPublisher - .receive(on: DispatchQueue.main) + .removeDuplicates() .sink { [weak self] roomSummaries in guard let self else { return } - - let invites = self.buildInvites(from: roomSummaries) - appSettings.seenInvites = Set(invites.map(\.roomDetails.id)) - self.state.invites = invites - fetchInviters(invites: invites) + fetchInvitersTask = Task { [weak self] in + guard let self else { return } + let fullInvites = await self.buildInvites(from: roomSummaries) + guard !Task.isCancelled else { return } + self.state.invites = fullInvites + self.state.isLoading = false + self.appSettings.seenInvites = Set(fullInvites.map(\.roomDetails.id)) + } } .store(in: &cancellables) } - - private func buildInvites(from summaries: [RoomSummary]) -> [InvitesScreenRoomDetails] { - summaries.compactMap { summary in - guard case .filled(let details) = summary else { - return nil - } - return InvitesScreenRoomDetails(roomDetails: details, isUnread: !previouslySeenInvites.contains(details.id)) - } - } - private func fetchInviters(invites: [InvitesScreenRoomDetails]) { - fetchInvitersTask = Task { [weak self] in - await withTaskGroup(of: (String, RoomMemberProxyProtocol)?.self) { group in - for invite in invites { - group.addTask { [weak self] in - let roomID = invite.roomDetails.id - guard let inviter = await self?.fetchInviter(for: roomID) else { + private func buildInvites(from summaries: [RoomSummary]) async -> [InvitesScreenRoomDetails] { + await Task.detached { + let invites: [InvitesScreenRoomDetails] = summaries.compactMap { summary in + guard case .filled(let details) = summary else { + return nil + } + return InvitesScreenRoomDetails(roomDetails: details, isUnread: !self.previouslySeenInvites.contains(details.id)) + } + + // fetch the inviters... + return await withTaskGroup(of: (Int, RoomMemberProxyProtocol)?.self) { [clientProxy = self.clientProxy] group in + var invitesWithInviters = invites + + for inviteIndex in 0.. RoomMemberProxyProtocol? { - await clientProxy.roomForIdentifier(roomID)?.inviter() + .value } private func startDeclineFlow(invite: InvitesScreenRoomDetails) { diff --git a/ElementX/Sources/Screens/InvitesScreen/View/InvitesScreen.swift b/ElementX/Sources/Screens/InvitesScreen/View/InvitesScreen.swift index 05c9672bc..1d45221de 100644 --- a/ElementX/Sources/Screens/InvitesScreen/View/InvitesScreen.swift +++ b/ElementX/Sources/Screens/InvitesScreen/View/InvitesScreen.swift @@ -20,6 +20,23 @@ struct InvitesScreen: View { @ObservedObject var context: InvitesScreenViewModel.Context var body: some View { + Group { + if context.viewState.isLoading { + ProgressView() + } else { + mainContent + } + } + .background(Color.compound.bgCanvasDefault.ignoresSafeArea()) + .navigationTitle(L10n.actionInvitesList) + .alert(item: $context.alertInfo) + .track(screen: .invites) + } + + // MARK: - Private + + @ViewBuilder + private var mainContent: some View { ScrollView { if !context.viewState.invites.isEmpty { LazyVStack(spacing: 0) { @@ -34,14 +51,8 @@ struct InvitesScreen: View { noInvitesContent } } - .background(Color.compound.bgCanvasDefault.ignoresSafeArea()) - .navigationTitle(L10n.actionInvitesList) - .alert(item: $context.alertInfo) - .track(screen: .invites) } - // MARK: - Private - private var noInvitesContent: some View { Text(L10n.screenInvitesEmptyList) .font(.compound.bodyLG) diff --git a/ElementX/Sources/Services/Room/RoomProxy.swift b/ElementX/Sources/Services/Room/RoomProxy.swift index 4c31c72d3..80ba33152 100644 --- a/ElementX/Sources/Services/Room/RoomProxy.swift +++ b/ElementX/Sources/Services/Room/RoomProxy.swift @@ -85,10 +85,10 @@ class RoomProxy: RoomProxyProtocol { subscribeToBackpagination() - innerTimelineProvider = RoomTimelineProvider(currentItems: result.items, - updatePublisher: updatesPublisher, - backPaginationStatePublisher: backPaginationStateSubject.eraseToAnyPublisher()) - + innerTimelineProvider = await RoomTimelineProvider(currentItems: result.items, + updatePublisher: updatesPublisher, + backPaginationStatePublisher: backPaginationStateSubject.eraseToAnyPublisher()) + Task { await fetchMembers() await updateMembers() @@ -209,7 +209,7 @@ class RoomProxy: RoomProxyProtocol { } func sendReadReceipt(for eventID: String) async -> Result { - sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) + sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) defer { sendMessageBackgroundTask?.stop() } @@ -229,7 +229,7 @@ class RoomProxy: RoomProxyProtocol { } func sendMessageEventContent(_ messageContent: RoomMessageEventContent) async -> Result { - sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) + sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) defer { sendMessageBackgroundTask?.stop() } @@ -243,7 +243,7 @@ class RoomProxy: RoomProxyProtocol { } func sendMessage(_ message: String, inReplyTo eventID: String? = nil) async -> Result { - sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) + sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) defer { sendMessageBackgroundTask?.stop() } @@ -266,7 +266,7 @@ class RoomProxy: RoomProxyProtocol { } func toggleReaction(_ reaction: String, to eventID: String) async -> Result { - sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) + sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) defer { sendMessageBackgroundTask?.stop() } @@ -286,7 +286,7 @@ class RoomProxy: RoomProxyProtocol { imageInfo: ImageInfo, progressSubject: CurrentValueSubject?, requestHandle: (SendAttachmentJoinHandleProtocol) -> Void) async -> Result { - sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) + sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) defer { sendMessageBackgroundTask?.stop() } @@ -311,7 +311,7 @@ class RoomProxy: RoomProxyProtocol { videoInfo: VideoInfo, progressSubject: CurrentValueSubject?, requestHandle: (SendAttachmentJoinHandleProtocol) -> Void) async -> Result { - sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) + sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) defer { sendMessageBackgroundTask?.stop() } @@ -335,7 +335,7 @@ class RoomProxy: RoomProxyProtocol { audioInfo: AudioInfo, progressSubject: CurrentValueSubject?, requestHandle: (SendAttachmentJoinHandleProtocol) -> Void) async -> Result { - sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) + sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) defer { sendMessageBackgroundTask?.stop() } @@ -359,7 +359,7 @@ class RoomProxy: RoomProxyProtocol { fileInfo: FileInfo, progressSubject: CurrentValueSubject?, requestHandle: (SendAttachmentJoinHandleProtocol) -> Void) async -> Result { - sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) + sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) defer { sendMessageBackgroundTask?.stop() } @@ -384,7 +384,7 @@ class RoomProxy: RoomProxyProtocol { description: String?, zoomLevel: UInt8?, assetType: AssetType?) async -> Result { - sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) + sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) defer { sendMessageBackgroundTask?.stop() } @@ -402,7 +402,7 @@ class RoomProxy: RoomProxyProtocol { } func retrySend(transactionID: String) async { - sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) + sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) defer { sendMessageBackgroundTask?.stop() } @@ -413,7 +413,7 @@ class RoomProxy: RoomProxyProtocol { } func cancelSend(transactionID: String) async { - sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) + sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) defer { sendMessageBackgroundTask?.stop() } @@ -424,7 +424,7 @@ class RoomProxy: RoomProxyProtocol { } func editMessage(_ newMessage: String, original eventID: String) async -> Result { - sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) + sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) defer { sendMessageBackgroundTask?.stop() } @@ -442,7 +442,7 @@ class RoomProxy: RoomProxyProtocol { } func redact(_ eventID: String) async -> Result { - sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) + sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) defer { sendMessageBackgroundTask?.stop() } @@ -460,7 +460,7 @@ class RoomProxy: RoomProxyProtocol { } func reportContent(_ eventID: String, reason: String?) async -> Result { - sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) + sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) defer { sendMessageBackgroundTask?.stop() } @@ -490,7 +490,7 @@ class RoomProxy: RoomProxyProtocol { } func getMember(userID: String) async -> Result { - sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) + sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) defer { sendMessageBackgroundTask?.stop() } @@ -506,7 +506,7 @@ class RoomProxy: RoomProxyProtocol { } func ignoreUser(_ userID: String) async -> Result { - sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) + sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) defer { sendMessageBackgroundTask?.stop() } @@ -528,7 +528,7 @@ class RoomProxy: RoomProxyProtocol { } func leaveRoom() async -> Result { - sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) + sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) defer { sendMessageBackgroundTask?.stop() } diff --git a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift index 9e8f16654..b33dbab6a 100644 --- a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift @@ -43,7 +43,6 @@ enum RoomProxyError: Error, Equatable { case failedUploadingAvatar } -@MainActor // sourcery: AutoMockable protocol RoomProxyProtocol { var id: String { get } diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProviderProtocol.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProviderProtocol.swift index 966f47962..a804163fd 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProviderProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProviderProtocol.swift @@ -41,7 +41,7 @@ enum RoomSummaryProviderState { } } -enum RoomSummary: CustomStringConvertible { +enum RoomSummary: CustomStringConvertible, Equatable { case empty case filled(details: RoomSummaryDetails) case invalidated(details: RoomSummaryDetails) @@ -74,6 +74,17 @@ enum RoomSummary: CustomStringConvertible { return "\(String(describing: Self.self)): Filled(\(details.id))" } } + + static func == (lhs: RoomSummary, rhs: RoomSummary) -> Bool { + switch (lhs, rhs) { + case (.empty, .empty): + return true + case (.filled(let lhsDetails), .filled(let rhsDetails)), (.invalidated(let lhsDetails), .invalidated(let rhsDetails)): + return lhsDetails.id == rhsDetails.id + default: + return false + } + } } protocol RoomSummaryProviderProtocol { diff --git a/UnitTests/Sources/HomeScreenViewModelTests.swift b/UnitTests/Sources/HomeScreenViewModelTests.swift index 5f6fd8085..ebbaba431 100644 --- a/UnitTests/Sources/HomeScreenViewModelTests.swift +++ b/UnitTests/Sources/HomeScreenViewModelTests.swift @@ -96,6 +96,7 @@ class HomeScreenViewModelTests: XCTestCase { func testLeaveRoomSuccess() async throws { let mockRoomId = "1" var correctResult = false + let expectation = expectation(description: #function) viewModel.callback = { result in switch result { case .roomLeft(let roomIdentifier): @@ -103,12 +104,13 @@ class HomeScreenViewModelTests: XCTestCase { default: break } + expectation.fulfill() } let room: RoomProxyMock = .init(with: .init(id: mockRoomId, displayName: "Some room")) room.leaveRoomClosure = { .success(()) } clientProxy.roomForIdentifierMocks[mockRoomId] = room context.send(viewAction: .confirmLeaveRoom(roomIdentifier: mockRoomId)) - await Task.yield() + await fulfillment(of: [expectation]) XCTAssertNil(context.alertInfo) XCTAssertTrue(correctResult) } diff --git a/UnitTests/Sources/ReportContentViewModelTests.swift b/UnitTests/Sources/ReportContentViewModelTests.swift index 11985f27e..8f47c960d 100644 --- a/UnitTests/Sources/ReportContentViewModelTests.swift +++ b/UnitTests/Sources/ReportContentViewModelTests.swift @@ -49,7 +49,7 @@ class ReportContentScreenViewModelTests: XCTestCase { XCTAssertNil(roomProxy.ignoreUserReceivedUserID, "The sender shouldn't have been ignored.") } - func testReportIgnoringSender() async { + func testReportIgnoringSender() async throws { // Given the report content view for some content. let roomProxy = RoomProxyMock(with: .init(displayName: "test")) roomProxy.reportContentReasonReturnValue = .success(()) @@ -61,9 +61,11 @@ class ReportContentScreenViewModelTests: XCTestCase { // When reporting the content and also ignoring the user. viewModel.state.bindings.reasonText = reportReason viewModel.state.bindings.ignoreUser = true + + let deferred = deferFulfillment(viewModel.actions.collect(2).first()) viewModel.context.send(viewAction: .submit) - - _ = await viewModel.actions.values.first() + let result = try await deferred.fulfill() + XCTAssertEqual(result, [.submitStarted, .submitFinished]) // Then the content should be reported, and the user should be ignored. XCTAssertEqual(roomProxy.reportContentReasonCallsCount, 1, "The content should always be reported.") diff --git a/UnitTests/Sources/RoomDetailsViewModelTests.swift b/UnitTests/Sources/RoomDetailsViewModelTests.swift index ac7d161ba..b9ffb5c8c 100644 --- a/UnitTests/Sources/RoomDetailsViewModelTests.swift +++ b/UnitTests/Sources/RoomDetailsViewModelTests.swift @@ -68,6 +68,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { } func testLeaveRoomSuccess() async { + let expectation = expectation(description: #function) roomProxyMock.leaveRoomClosure = { .success(()) } @@ -78,18 +79,23 @@ class RoomDetailsScreenViewModelTests: XCTestCase { default: XCTFail("leftRoom expected") } + expectation.fulfill() } context.send(viewAction: .confirmLeave) - await Task.yield() + await fulfillment(of: [expectation]) XCTAssertEqual(roomProxyMock.leaveRoomCallsCount, 1) } func testLeaveRoomError() async { + let expectation = expectation(description: #function) roomProxyMock.leaveRoomClosure = { - .failure(.failedLeavingRoom) + defer { + expectation.fulfill() + } + return .failure(.failedLeavingRoom) } context.send(viewAction: .confirmLeave) - await context.nextViewState() + await fulfillment(of: [expectation]) XCTAssertEqual(roomProxyMock.leaveRoomCallsCount, 1) XCTAssertNotNil(context.alertInfo) } diff --git a/UnitTests/Sources/RoomScreenViewModelTests.swift b/UnitTests/Sources/RoomScreenViewModelTests.swift index ccb8eeba8..f1d9924b1 100644 --- a/UnitTests/Sources/RoomScreenViewModelTests.swift +++ b/UnitTests/Sources/RoomScreenViewModelTests.swift @@ -178,6 +178,7 @@ class RoomScreenViewModelTests: XCTestCase { func testGoToUserDetailsSuccessNoDelay() async { // Setup + let expectation = expectation(description: #function) let timelineController = MockRoomTimelineController() let roomProxyMock = RoomProxyMock(with: .init(displayName: "")) let roomMemberMock = RoomMemberProxyMock() @@ -197,11 +198,12 @@ class RoomScreenViewModelTests: XCTestCase { default: XCTFail("Did not received the expected action") } + expectation.fulfill() } // Test viewModel.context.send(viewAction: .tappedOnUser(userID: "bob")) - await Task.yield() + await fulfillment(of: [expectation]) XCTAssert(userIndicatorControllerMock.submitIndicatorDelayCallsCount == 1) XCTAssert(roomProxyMock.getMemberUserIDCallsCount == 1) XCTAssertEqual(roomProxyMock.getMemberUserIDReceivedUserID, "bob") @@ -288,7 +290,7 @@ class RoomScreenViewModelTests: XCTestCase { // Test viewModel.context.send(viewAction: .retrySend(transactionID: "test retry send id")) - await Task.yield() + try? await Task.sleep(for: .microseconds(500)) XCTAssert(roomProxyMock.retrySendTransactionIDCallsCount == 1) XCTAssert(roomProxyMock.retrySendTransactionIDReceivedInvocations == ["test retry send id"]) } @@ -325,7 +327,7 @@ class RoomScreenViewModelTests: XCTestCase { // Test viewModel.context.send(viewAction: .cancelSend(transactionID: "test cancel send id")) - await Task.yield() + try? await Task.sleep(for: .microseconds(500)) XCTAssert(roomProxyMock.cancelSendTransactionIDCallsCount == 1) XCTAssert(roomProxyMock.cancelSendTransactionIDReceivedInvocations == ["test cancel send id"]) }