feat: report a room

updated tests

updated tests

added a feature flag to report room

removed delay

rename
This commit is contained in:
Mauro Romito
2025-04-02 17:44:24 +02:00
committed by Mauro
parent 8149107aef
commit 2ee5c1b403
31 changed files with 750 additions and 104 deletions
+40
View File
@@ -95,6 +95,7 @@
0EEC614342F823E5BF966C2C /* AppLockTimerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A5B4CD611DE7E94F5BA87B2 /* AppLockTimerTests.swift */; }; 0EEC614342F823E5BF966C2C /* AppLockTimerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A5B4CD611DE7E94F5BA87B2 /* AppLockTimerTests.swift */; };
0F4709282FCCFBEFED427B8A /* AuthenticationClientBuilderMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4760CE2128FBC217304272AB /* AuthenticationClientBuilderMock.swift */; }; 0F4709282FCCFBEFED427B8A /* AuthenticationClientBuilderMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4760CE2128FBC217304272AB /* AuthenticationClientBuilderMock.swift */; };
0F6C8033FA60CFD36F7CA205 /* AppLockSetupPINScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A019A12C866D64CF072024B9 /* AppLockSetupPINScreenViewModel.swift */; }; 0F6C8033FA60CFD36F7CA205 /* AppLockSetupPINScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A019A12C866D64CF072024B9 /* AppLockSetupPINScreenViewModel.swift */; };
0FA03F5A33C0857231B32B44 /* ReportRoomScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1FDB0B87D925AE830E32621 /* ReportRoomScreenViewModel.swift */; };
108D3C0707A90B0F848CDBB9 /* ResolveVerifiedUserSendFailureScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60011EF0086E49DBD78E16E5 /* ResolveVerifiedUserSendFailureScreenModels.swift */; }; 108D3C0707A90B0F848CDBB9 /* ResolveVerifiedUserSendFailureScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60011EF0086E49DBD78E16E5 /* ResolveVerifiedUserSendFailureScreenModels.swift */; };
109AEB7D33C4497727AFB87F /* TimelineInteractionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BA894BC09972DC45E497D37 /* TimelineInteractionHandler.swift */; }; 109AEB7D33C4497727AFB87F /* TimelineInteractionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BA894BC09972DC45E497D37 /* TimelineInteractionHandler.swift */; };
10D60D287025B71F4743A425 /* RoomDirectorySearchProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 471BB7276C97AF60B3A5463B /* RoomDirectorySearchProxy.swift */; }; 10D60D287025B71F4743A425 /* RoomDirectorySearchProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 471BB7276C97AF60B3A5463B /* RoomDirectorySearchProxy.swift */; };
@@ -138,6 +139,7 @@
1795EA6A6C4942CAE0459DF0 /* SecureBackupKeyBackupScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82B612853BFB68373249777B /* SecureBackupKeyBackupScreenViewModel.swift */; }; 1795EA6A6C4942CAE0459DF0 /* SecureBackupKeyBackupScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82B612853BFB68373249777B /* SecureBackupKeyBackupScreenViewModel.swift */; };
17BC15DA08A52587466698C5 /* RoomMessageEventStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80E815FF3CC5E5A355E3A25E /* RoomMessageEventStringBuilder.swift */; }; 17BC15DA08A52587466698C5 /* RoomMessageEventStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80E815FF3CC5E5A355E3A25E /* RoomMessageEventStringBuilder.swift */; };
1801F1467ABCEA080419E150 /* preview_avatar_user.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 87FC42213E86E8182CFD3A49 /* preview_avatar_user.jpg */; }; 1801F1467ABCEA080419E150 /* preview_avatar_user.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 87FC42213E86E8182CFD3A49 /* preview_avatar_user.jpg */; };
182D532B736178A1DED9F76E /* ReportRoomScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11FCAE847556719BBE7A0882 /* ReportRoomScreenModels.swift */; };
18867F4F1C8991EEC56EA932 /* UTType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 897DF5E9A70CE05A632FC8AF /* UTType.swift */; }; 18867F4F1C8991EEC56EA932 /* UTType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 897DF5E9A70CE05A632FC8AF /* UTType.swift */; };
18978C9438206828C1D5AF2A /* test_animated_image.gif in Resources */ = {isa = PBXBuildFile; fileRef = 53FD6D3D38F556CEAA280C58 /* test_animated_image.gif */; }; 18978C9438206828C1D5AF2A /* test_animated_image.gif in Resources */ = {isa = PBXBuildFile; fileRef = 53FD6D3D38F556CEAA280C58 /* test_animated_image.gif */; };
18E3786918486D4C9726BC84 /* FormButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89FBFC09F9DAFF1E4BA97849 /* FormButtonStyles.swift */; }; 18E3786918486D4C9726BC84 /* FormButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89FBFC09F9DAFF1E4BA97849 /* FormButtonStyles.swift */; };
@@ -162,6 +164,7 @@
1B8E30B35BF8F541C1318F19 /* SecureBackupScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40316EFFEAC7B206EE9A55AE /* SecureBackupScreenViewModelTests.swift */; }; 1B8E30B35BF8F541C1318F19 /* SecureBackupScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40316EFFEAC7B206EE9A55AE /* SecureBackupScreenViewModelTests.swift */; };
1BA04D05EBC6646958B1BE60 /* PlaceholderScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF34A2FD6797535C95AC918D /* PlaceholderScreenCoordinator.swift */; }; 1BA04D05EBC6646958B1BE60 /* PlaceholderScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF34A2FD6797535C95AC918D /* PlaceholderScreenCoordinator.swift */; };
1C409A26A99F0371C47AFA51 /* UserDiscoveryServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F615A00DB223FF3280204D2 /* UserDiscoveryServiceProtocol.swift */; }; 1C409A26A99F0371C47AFA51 /* UserDiscoveryServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F615A00DB223FF3280204D2 /* UserDiscoveryServiceProtocol.swift */; };
1C598D3B785645AAC7B35760 /* ReportRoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292EEE1F71DCC205C45728F7 /* ReportRoomScreenCoordinator.swift */; };
1C8BC70A18060677E295A846 /* ShareToMapsAppActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4481799F455B3DA243BDA2AC /* ShareToMapsAppActivity.swift */; }; 1C8BC70A18060677E295A846 /* ShareToMapsAppActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4481799F455B3DA243BDA2AC /* ShareToMapsAppActivity.swift */; };
1C9BB74711E5F24C77B7FED0 /* RoomMembersListScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AEA0B743847CFA5B3C38EE4 /* RoomMembersListScreenCoordinator.swift */; }; 1C9BB74711E5F24C77B7FED0 /* RoomMembersListScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AEA0B743847CFA5B3C38EE4 /* RoomMembersListScreenCoordinator.swift */; };
1D5DC685CED904386C89B7DA /* NSRegularExpresion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95BAC0F6C9644336E9567EE6 /* NSRegularExpresion.swift */; }; 1D5DC685CED904386C89B7DA /* NSRegularExpresion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95BAC0F6C9644336E9567EE6 /* NSRegularExpresion.swift */; };
@@ -218,6 +221,7 @@
298F9EC30E918F12AB7F1EE8 /* TypingIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81F0325E252B057FAEEE1B2D /* TypingIndicatorView.swift */; }; 298F9EC30E918F12AB7F1EE8 /* TypingIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81F0325E252B057FAEEE1B2D /* TypingIndicatorView.swift */; };
29EE1791E0AFA1ABB7F23D2F /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 3853B78FB8531B83936C5DA6 /* SwiftState */; }; 29EE1791E0AFA1ABB7F23D2F /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 3853B78FB8531B83936C5DA6 /* SwiftState */; };
2A56B00B070F83E0FE571193 /* TimelineMediaPreviewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B18A454132A5A5247802821E /* TimelineMediaPreviewDataSource.swift */; }; 2A56B00B070F83E0FE571193 /* TimelineMediaPreviewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B18A454132A5A5247802821E /* TimelineMediaPreviewDataSource.swift */; };
2A61D2B4A225332CECA3B937 /* ReportRoomScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20403084A320D588ACED200 /* ReportRoomScreenViewModelProtocol.swift */; };
2AAB2A77F1762A2648078A30 /* InteractiveQuickLook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 638A81B97D51591D0FCFA598 /* InteractiveQuickLook.swift */; }; 2AAB2A77F1762A2648078A30 /* InteractiveQuickLook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 638A81B97D51591D0FCFA598 /* InteractiveQuickLook.swift */; };
2AB9D4146C8748CF1D007B67 /* test_pdf.pdf in Resources */ = {isa = PBXBuildFile; fileRef = BE98688578F8B0541D853695 /* test_pdf.pdf */; }; 2AB9D4146C8748CF1D007B67 /* test_pdf.pdf in Resources */ = {isa = PBXBuildFile; fileRef = BE98688578F8B0541D853695 /* test_pdf.pdf */; };
2AED12987603157C32C2114D /* TimelineBubbleLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5D8FEB1FED10E995CB002F7 /* TimelineBubbleLayout.swift */; }; 2AED12987603157C32C2114D /* TimelineBubbleLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5D8FEB1FED10E995CB002F7 /* TimelineBubbleLayout.swift */; };
@@ -415,6 +419,7 @@
50539366B408780B232C1910 /* EstimatedWaveformView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD0FF64B0E6470F66F42E182 /* EstimatedWaveformView.swift */; }; 50539366B408780B232C1910 /* EstimatedWaveformView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD0FF64B0E6470F66F42E182 /* EstimatedWaveformView.swift */; };
5100F53E6884A15F9BA07CC3 /* AttributedStringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CA26F55123E36B50DB0B3A /* AttributedStringTests.swift */; }; 5100F53E6884A15F9BA07CC3 /* AttributedStringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CA26F55123E36B50DB0B3A /* AttributedStringTests.swift */; };
5139F4BD5A5DF6F8D11A9BDE /* NotificationPermissionsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46D0BA44B1838E65B507B277 /* NotificationPermissionsScreen.swift */; }; 5139F4BD5A5DF6F8D11A9BDE /* NotificationPermissionsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46D0BA44B1838E65B507B277 /* NotificationPermissionsScreen.swift */; };
513AF15E0E84711B80D04B1B /* ReportRoomScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C3E9684DCE6B66BD0B5DF67 /* ReportRoomScreenViewModelTests.swift */; };
518C93DC6516D3D018DE065F /* UNNotificationRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E751D7EDB6043238111D90 /* UNNotificationRequest.swift */; }; 518C93DC6516D3D018DE065F /* UNNotificationRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E751D7EDB6043238111D90 /* UNNotificationRequest.swift */; };
51B3B19FA5F91B455C807BA7 /* RoomPollsHistoryScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E964AF2DFEB31E2B799999F /* RoomPollsHistoryScreenModels.swift */; }; 51B3B19FA5F91B455C807BA7 /* RoomPollsHistoryScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E964AF2DFEB31E2B799999F /* RoomPollsHistoryScreenModels.swift */; };
523C6800ED85D5810CF18C19 /* OIDCAccountSettingsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D737F4672021D0A7D218CD /* OIDCAccountSettingsPresenter.swift */; }; 523C6800ED85D5810CF18C19 /* OIDCAccountSettingsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D737F4672021D0A7D218CD /* OIDCAccountSettingsPresenter.swift */; };
@@ -797,6 +802,7 @@
9C55746D8F6A3E35CFCF4A7A /* AuthenticationStartLogo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 598F01EBD0C4CC550C644418 /* AuthenticationStartLogo.swift */; }; 9C55746D8F6A3E35CFCF4A7A /* AuthenticationStartLogo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 598F01EBD0C4CC550C644418 /* AuthenticationStartLogo.swift */; };
9C63171267E22FEB288EC860 /* RoomHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1627F2D56477BD331F6D732C /* RoomHeaderView.swift */; }; 9C63171267E22FEB288EC860 /* RoomHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1627F2D56477BD331F6D732C /* RoomHeaderView.swift */; };
9CBB04365408F9D6F46BA3A7 /* PinnedEventsTimelineFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A54AAF72E821B4084B7E4298 /* PinnedEventsTimelineFlowCoordinator.swift */; }; 9CBB04365408F9D6F46BA3A7 /* PinnedEventsTimelineFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A54AAF72E821B4084B7E4298 /* PinnedEventsTimelineFlowCoordinator.swift */; };
9CCF6711DD50BFF8B5ACE9CF /* ReportRoomScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 276833816F5DEB1CE3B8BE1E /* ReportRoomScreen.swift */; };
9D2E03DB175A6AB14589076D /* AnalyticsEvents in Frameworks */ = {isa = PBXBuildFile; productRef = 2A3F7BCCB18C15B30CCA39A9 /* AnalyticsEvents */; }; 9D2E03DB175A6AB14589076D /* AnalyticsEvents in Frameworks */ = {isa = PBXBuildFile; productRef = 2A3F7BCCB18C15B30CCA39A9 /* AnalyticsEvents */; };
9D79B94493FB32249F7E472F /* PlaceholderAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C705E605EF57C19DBE86FFA1 /* PlaceholderAvatarImage.swift */; }; 9D79B94493FB32249F7E472F /* PlaceholderAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C705E605EF57C19DBE86FFA1 /* PlaceholderAvatarImage.swift */; };
9D9690D2FD4CD26FF670620F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C75EF87651B00A176AB08E97 /* AppDelegate.swift */; }; 9D9690D2FD4CD26FF670620F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C75EF87651B00A176AB08E97 /* AppDelegate.swift */; };
@@ -1408,6 +1414,7 @@
0BCE3FAF40932AC7C7639AC4 /* AnalyticsSettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenViewModel.swift; sourceTree = "<group>"; }; 0BCE3FAF40932AC7C7639AC4 /* AnalyticsSettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenViewModel.swift; sourceTree = "<group>"; };
0BD116096CAA9139B95EEA9C /* UserProfileScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileScreenViewModel.swift; sourceTree = "<group>"; }; 0BD116096CAA9139B95EEA9C /* UserProfileScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileScreenViewModel.swift; sourceTree = "<group>"; };
0C34667458773B02AB5FB0B2 /* LegalInformationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenViewModel.swift; sourceTree = "<group>"; }; 0C34667458773B02AB5FB0B2 /* LegalInformationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenViewModel.swift; sourceTree = "<group>"; };
0C3E9684DCE6B66BD0B5DF67 /* ReportRoomScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportRoomScreenViewModelTests.swift; sourceTree = "<group>"; };
0C62E07C1164F5120727A2A8 /* AppLockSetupBiometricsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupBiometricsScreenCoordinator.swift; sourceTree = "<group>"; }; 0C62E07C1164F5120727A2A8 /* AppLockSetupBiometricsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupBiometricsScreenCoordinator.swift; sourceTree = "<group>"; };
0CB569EAA5017B5B23970655 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Localizable.strings; sourceTree = "<group>"; }; 0CB569EAA5017B5B23970655 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Localizable.strings; sourceTree = "<group>"; };
0CCC6C31102E1D8B9106DEDE /* AppLockSetupBiometricsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupBiometricsScreenViewModelProtocol.swift; sourceTree = "<group>"; }; 0CCC6C31102E1D8B9106DEDE /* AppLockSetupBiometricsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupBiometricsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
@@ -1433,6 +1440,7 @@
11151E78D6BB2B04A8FBD389 /* EmojiPickerScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenViewModelProtocol.swift; sourceTree = "<group>"; }; 11151E78D6BB2B04A8FBD389 /* EmojiPickerScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenViewModelProtocol.swift; sourceTree = "<group>"; };
111B698739E3410E2CDB7144 /* MXLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXLog.swift; sourceTree = "<group>"; }; 111B698739E3410E2CDB7144 /* MXLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXLog.swift; sourceTree = "<group>"; };
113356152C099951A6D17D85 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Localizable.strings; sourceTree = "<group>"; }; 113356152C099951A6D17D85 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Localizable.strings; sourceTree = "<group>"; };
11FCAE847556719BBE7A0882 /* ReportRoomScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportRoomScreenModels.swift; sourceTree = "<group>"; };
1215A4FC53D2319E81AE8970 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; }; 1215A4FC53D2319E81AE8970 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
1222DB76B917EB8A55365BA5 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; }; 1222DB76B917EB8A55365BA5 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
127A57D053CE8C87B5EFB089 /* Consumable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Consumable.swift; sourceTree = "<group>"; }; 127A57D053CE8C87B5EFB089 /* Consumable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Consumable.swift; sourceTree = "<group>"; };
@@ -1548,6 +1556,7 @@
2711E5996016ABD6EAAEB58A /* LogLevel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogLevel.swift; sourceTree = "<group>"; }; 2711E5996016ABD6EAAEB58A /* LogLevel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogLevel.swift; sourceTree = "<group>"; };
2721D7B051F0159AA919DA05 /* RoomChangePermissionsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangePermissionsScreenViewModelProtocol.swift; sourceTree = "<group>"; }; 2721D7B051F0159AA919DA05 /* RoomChangePermissionsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomChangePermissionsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
2757B1BE23DF8AA239937243 /* AudioConverterProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioConverterProtocol.swift; sourceTree = "<group>"; }; 2757B1BE23DF8AA239937243 /* AudioConverterProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioConverterProtocol.swift; sourceTree = "<group>"; };
276833816F5DEB1CE3B8BE1E /* ReportRoomScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportRoomScreen.swift; sourceTree = "<group>"; };
277C20CDD5B64510401B6D0D /* ServerConfigurationScreenViewStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfigurationScreenViewStateTests.swift; sourceTree = "<group>"; }; 277C20CDD5B64510401B6D0D /* ServerConfigurationScreenViewStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfigurationScreenViewStateTests.swift; sourceTree = "<group>"; };
27A1AD6389A4659AF0CEAE62 /* NotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtension.swift; sourceTree = "<group>"; }; 27A1AD6389A4659AF0CEAE62 /* NotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtension.swift; sourceTree = "<group>"; };
27A9E3FBE8A66B5A17AD7F74 /* AppRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRoutes.swift; sourceTree = "<group>"; }; 27A9E3FBE8A66B5A17AD7F74 /* AppRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRoutes.swift; sourceTree = "<group>"; };
@@ -1561,6 +1570,7 @@
28C202C1C7E330F124981A31 /* GenericCallLinkWidgetDriver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericCallLinkWidgetDriver.swift; sourceTree = "<group>"; }; 28C202C1C7E330F124981A31 /* GenericCallLinkWidgetDriver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericCallLinkWidgetDriver.swift; sourceTree = "<group>"; };
28EA8BE9EEDBD17555141C7E /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = el; path = el.lproj/Localizable.stringsdict; sourceTree = "<group>"; }; 28EA8BE9EEDBD17555141C7E /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = el; path = el.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
2910422CB628D3B2BBE47449 /* SeparatorRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorRoomTimelineView.swift; sourceTree = "<group>"; }; 2910422CB628D3B2BBE47449 /* SeparatorRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorRoomTimelineView.swift; sourceTree = "<group>"; };
292EEE1F71DCC205C45728F7 /* ReportRoomScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportRoomScreenCoordinator.swift; sourceTree = "<group>"; };
29A953B6C0C431DBF4DD00B4 /* RoomSummary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummary.swift; sourceTree = "<group>"; }; 29A953B6C0C431DBF4DD00B4 /* RoomSummary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummary.swift; sourceTree = "<group>"; };
2A2BB38DF61F5100B8723112 /* TimelineMediaPreviewModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMediaPreviewModels.swift; sourceTree = "<group>"; }; 2A2BB38DF61F5100B8723112 /* TimelineMediaPreviewModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMediaPreviewModels.swift; sourceTree = "<group>"; };
2A5C6FBF97B6EED3D4FA5EFF /* AttributedStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringBuilder.swift; sourceTree = "<group>"; }; 2A5C6FBF97B6EED3D4FA5EFF /* AttributedStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringBuilder.swift; sourceTree = "<group>"; };
@@ -2201,6 +2211,7 @@
B172057567E049007A5C4D92 /* Strings+SAS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Strings+SAS.swift"; sourceTree = "<group>"; }; B172057567E049007A5C4D92 /* Strings+SAS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Strings+SAS.swift"; sourceTree = "<group>"; };
B18A454132A5A5247802821E /* TimelineMediaPreviewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMediaPreviewDataSource.swift; sourceTree = "<group>"; }; B18A454132A5A5247802821E /* TimelineMediaPreviewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMediaPreviewDataSource.swift; sourceTree = "<group>"; };
B1E227F34BE43B08E098796E /* TestablePreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestablePreview.swift; sourceTree = "<group>"; }; B1E227F34BE43B08E098796E /* TestablePreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestablePreview.swift; sourceTree = "<group>"; };
B1FDB0B87D925AE830E32621 /* ReportRoomScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportRoomScreenViewModel.swift; sourceTree = "<group>"; };
B251F5B4511D1CA0BA8361FE /* CoordinatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoordinatorProtocol.swift; sourceTree = "<group>"; }; B251F5B4511D1CA0BA8361FE /* CoordinatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoordinatorProtocol.swift; sourceTree = "<group>"; };
B2AD8A56CD37E23071A2F4BF /* PHGPostHogMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogMock.swift; sourceTree = "<group>"; }; B2AD8A56CD37E23071A2F4BF /* PHGPostHogMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogMock.swift; sourceTree = "<group>"; };
B2AF1828A5B76B7C371240FE /* StateRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRoomTimelineView.swift; sourceTree = "<group>"; }; B2AF1828A5B76B7C371240FE /* StateRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRoomTimelineView.swift; sourceTree = "<group>"; };
@@ -2426,6 +2437,7 @@
E1A5FEF17ED7E6176D922D4F /* RoomDetailsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreen.swift; sourceTree = "<group>"; }; E1A5FEF17ED7E6176D922D4F /* RoomDetailsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreen.swift; sourceTree = "<group>"; };
E1E0B4A34E69BD2132BEC521 /* MessageText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageText.swift; sourceTree = "<group>"; }; E1E0B4A34E69BD2132BEC521 /* MessageText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageText.swift; sourceTree = "<group>"; };
E1ED17433ADC77287F8904F9 /* CallNotificationRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallNotificationRoomTimelineItem.swift; sourceTree = "<group>"; }; E1ED17433ADC77287F8904F9 /* CallNotificationRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallNotificationRoomTimelineItem.swift; sourceTree = "<group>"; };
E20403084A320D588ACED200 /* ReportRoomScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportRoomScreenViewModelProtocol.swift; sourceTree = "<group>"; };
E2520C4F33AA0C061D209C28 /* RoomMembersListScreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenTests.swift; sourceTree = "<group>"; }; E2520C4F33AA0C061D209C28 /* RoomMembersListScreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenTests.swift; sourceTree = "<group>"; };
E2776E63E02719B20758EB78 /* EditRoomAddressListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditRoomAddressListRow.swift; sourceTree = "<group>"; }; E2776E63E02719B20758EB78 /* EditRoomAddressListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditRoomAddressListRow.swift; sourceTree = "<group>"; };
E2B1CC9AA154F4D5435BF60A /* Comparable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Comparable.swift; sourceTree = "<group>"; }; E2B1CC9AA154F4D5435BF60A /* Comparable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Comparable.swift; sourceTree = "<group>"; };
@@ -3093,6 +3105,14 @@
path = AppLockSetupSettingsScreen; path = AppLockSetupSettingsScreen;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
2B27F01BB7B839E543DFE025 /* View */ = {
isa = PBXGroup;
children = (
276833816F5DEB1CE3B8BE1E /* ReportRoomScreen.swift */,
);
path = View;
sourceTree = "<group>";
};
2C0F49BD446849654C0D24E0 /* RoomMember */ = { 2C0F49BD446849654C0D24E0 /* RoomMember */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -3765,6 +3785,18 @@
path = RoomMembershipDetails; path = RoomMembershipDetails;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
4DDB6973795DA09189CF64A5 /* ReportRoomScreen */ = {
isa = PBXGroup;
children = (
292EEE1F71DCC205C45728F7 /* ReportRoomScreenCoordinator.swift */,
11FCAE847556719BBE7A0882 /* ReportRoomScreenModels.swift */,
B1FDB0B87D925AE830E32621 /* ReportRoomScreenViewModel.swift */,
E20403084A320D588ACED200 /* ReportRoomScreenViewModelProtocol.swift */,
2B27F01BB7B839E543DFE025 /* View */,
);
path = ReportRoomScreen;
sourceTree = "<group>";
};
4EC4EBBC4F6885775F198875 /* Sources */ = { 4EC4EBBC4F6885775F198875 /* Sources */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -4247,6 +4279,7 @@
347D708104CCEF771428C9A3 /* PollFormScreenViewModelTests.swift */, 347D708104CCEF771428C9A3 /* PollFormScreenViewModelTests.swift */,
25E7E9B7FEAB6169D960C206 /* QRCodeLoginScreenViewModelTests.swift */, 25E7E9B7FEAB6169D960C206 /* QRCodeLoginScreenViewModelTests.swift */,
086C19086DD16E9B38E25954 /* ReportContentViewModelTests.swift */, 086C19086DD16E9B38E25954 /* ReportContentViewModelTests.swift */,
0C3E9684DCE6B66BD0B5DF67 /* ReportRoomScreenViewModelTests.swift */,
57084488B03BDB33C7B7CA0E /* ResolveVerifiedUserSendFailureScreenViewModelTests.swift */, 57084488B03BDB33C7B7CA0E /* ResolveVerifiedUserSendFailureScreenViewModelTests.swift */,
A7978C9EFBDD7DE39BD86726 /* RestorationTokenTests.swift */, A7978C9EFBDD7DE39BD86726 /* RestorationTokenTests.swift */,
41D041A857614A9AE13C7795 /* RoomChangePermissionsScreenViewModelTests.swift */, 41D041A857614A9AE13C7795 /* RoomChangePermissionsScreenViewModelTests.swift */,
@@ -5718,6 +5751,7 @@
3E535010B850B53DDD3CFF2A /* PinnedEventsTimelineScreen */, 3E535010B850B53DDD3CFF2A /* PinnedEventsTimelineScreen */,
3D733E8352DD4C461CFD8B8A /* QRCodeLoginScreen */, 3D733E8352DD4C461CFD8B8A /* QRCodeLoginScreen */,
5970F275D6014548DCED6106 /* ReportContentScreen */, 5970F275D6014548DCED6106 /* ReportContentScreen */,
4DDB6973795DA09189CF64A5 /* ReportRoomScreen */,
A040ACE4D778FFCD65DDF5F8 /* ResolveVerifiedUserSendFailureScreen */, A040ACE4D778FFCD65DDF5F8 /* ResolveVerifiedUserSendFailureScreen */,
DAB7DC51866A6D1B51BDC3A2 /* RoomChangePermissionsScreen */, DAB7DC51866A6D1B51BDC3A2 /* RoomChangePermissionsScreen */,
D8388454B5909D862CAC78F7 /* RoomChangeRolesScreen */, D8388454B5909D862CAC78F7 /* RoomChangeRolesScreen */,
@@ -6731,6 +6765,7 @@
E3EBC3BF7CE3960B41757BAA /* Publisher.swift in Sources */, E3EBC3BF7CE3960B41757BAA /* Publisher.swift in Sources */,
BDC4EB54CC3036730475CB8B /* QRCodeLoginScreenViewModelTests.swift in Sources */, BDC4EB54CC3036730475CB8B /* QRCodeLoginScreenViewModelTests.swift in Sources */,
D53B80EF02C1062E68659EDD /* ReportContentViewModelTests.swift in Sources */, D53B80EF02C1062E68659EDD /* ReportContentViewModelTests.swift in Sources */,
513AF15E0E84711B80D04B1B /* ReportRoomScreenViewModelTests.swift in Sources */,
09D3D7D115318CAD131B4FE7 /* ResolveVerifiedUserSendFailureScreenViewModelTests.swift in Sources */, 09D3D7D115318CAD131B4FE7 /* ResolveVerifiedUserSendFailureScreenViewModelTests.swift in Sources */,
C5627BCC3EBBB96A943B6D93 /* RestorationTokenTests.swift in Sources */, C5627BCC3EBBB96A943B6D93 /* RestorationTokenTests.swift in Sources */,
9B03943616A1147539DF7F08 /* RoomChangePermissionsScreenViewModelTests.swift in Sources */, 9B03943616A1147539DF7F08 /* RoomChangePermissionsScreenViewModelTests.swift in Sources */,
@@ -7347,6 +7382,11 @@
46A261AA898344A1F3C406B1 /* ReportContentScreenModels.swift in Sources */, 46A261AA898344A1F3C406B1 /* ReportContentScreenModels.swift in Sources */,
42A5A42ACF063EEE6B1980D2 /* ReportContentScreenViewModel.swift in Sources */, 42A5A42ACF063EEE6B1980D2 /* ReportContentScreenViewModel.swift in Sources */,
8285FF4B2C2331758C437FF7 /* ReportContentScreenViewModelProtocol.swift in Sources */, 8285FF4B2C2331758C437FF7 /* ReportContentScreenViewModelProtocol.swift in Sources */,
9CCF6711DD50BFF8B5ACE9CF /* ReportRoomScreen.swift in Sources */,
1C598D3B785645AAC7B35760 /* ReportRoomScreenCoordinator.swift in Sources */,
182D532B736178A1DED9F76E /* ReportRoomScreenModels.swift in Sources */,
0FA03F5A33C0857231B32B44 /* ReportRoomScreenViewModel.swift in Sources */,
2A61D2B4A225332CECA3B937 /* ReportRoomScreenViewModelProtocol.swift in Sources */,
4715FE33667C5899E64DD0E6 /* ResolveVerifiedUserSendFailureScreen.swift in Sources */, 4715FE33667C5899E64DD0E6 /* ResolveVerifiedUserSendFailureScreen.swift in Sources */,
583A41A4BE76E2E9E0B97881 /* ResolveVerifiedUserSendFailureScreenCoordinator.swift in Sources */, 583A41A4BE76E2E9E0B97881 /* ResolveVerifiedUserSendFailureScreenCoordinator.swift in Sources */,
108D3C0707A90B0F848CDBB9 /* ResolveVerifiedUserSendFailureScreenModels.swift in Sources */, 108D3C0707A90B0F848CDBB9 /* ResolveVerifiedUserSendFailureScreenModels.swift in Sources */,
@@ -56,6 +56,7 @@ final class AppSettings {
case fuzzyRoomListSearchEnabled case fuzzyRoomListSearchEnabled
case enableOnlySignedDeviceIsolationMode case enableOnlySignedDeviceIsolationMode
case knockingEnabled case knockingEnabled
case reportRoomEnabled
} }
private static var suiteName: String = InfoPlistReader.main.appGroupIdentifier private static var suiteName: String = InfoPlistReader.main.appGroupIdentifier
@@ -330,6 +331,8 @@ final class AppSettings {
@UserPreference(key: UserDefaultsKeys.knockingEnabled, defaultValue: false, storageType: .userDefaults(store)) @UserPreference(key: UserDefaultsKeys.knockingEnabled, defaultValue: false, storageType: .userDefaults(store))
var knockingEnabled var knockingEnabled
@UserPreference(key: UserDefaultsKeys.reportRoomEnabled, defaultValue: false, storageType: .userDefaults(store)) var reportRoomEnabled
#endif #endif
// MARK: - Shared // MARK: - Shared
@@ -414,6 +414,11 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
return .securityAndPrivacy(previousState: fromState) return .securityAndPrivacy(previousState: fromState)
case (.securityAndPrivacy(let previousState), .dismissSecurityAndPrivacyScreen): case (.securityAndPrivacy(let previousState), .dismissSecurityAndPrivacyScreen):
return previousState return previousState
case (.roomDetails, .presentReportRoomScreen):
return .reportRoom(previousState: fromState)
case (.reportRoom(let previousState), .dismissReportRoomScreen):
return previousState
default: default:
return nil return nil
@@ -585,6 +590,11 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
presentSecurityAndPrivacyScreen() presentSecurityAndPrivacyScreen()
case (.securityAndPrivacy, .dismissSecurityAndPrivacyScreen, .roomDetails): case (.securityAndPrivacy, .dismissSecurityAndPrivacyScreen, .roomDetails):
break break
case (.roomDetails, .presentReportRoomScreen, .reportRoom):
presentReportRoom()
case (.reportRoom, .dismissReportRoomScreen, .roomDetails):
break
// Child flow // Child flow
case (_, .startChildFlow(let roomID, let via, let entryPoint), .presentingChild): case (_, .startChildFlow(let roomID, let via, let entryPoint), .presentingChild):
@@ -869,6 +879,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
stateMachine.tryEvent(.presentSecurityAndPrivacyScreen) stateMachine.tryEvent(.presentSecurityAndPrivacyScreen)
case .presentRecipientDetails(let userID): case .presentRecipientDetails(let userID):
stateMachine.tryEvent(.presentRoomMemberDetails(userID: userID)) stateMachine.tryEvent(.presentRoomMemberDetails(userID: userID))
case .presentReportRoomScreen:
stateMachine.tryEvent(.presentReportRoomScreen)
} }
} }
.store(in: &cancellables) .store(in: &cancellables)
@@ -1516,6 +1528,29 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
navigationStackCoordinator.setSheetCoordinator(stackCoordinator) navigationStackCoordinator.setSheetCoordinator(stackCoordinator)
} }
private func presentReportRoom() {
let stackCoordinator = NavigationStackCoordinator()
let coordinator = ReportRoomScreenCoordinator(parameters: .init(roomProxy: roomProxy,
userIndicatorController: userIndicatorController))
coordinator.actionsPublisher.sink { [weak self] action in
guard let self else { return }
switch action {
case .dismiss(let shouldLeaveRoom):
if shouldLeaveRoom {
stateMachine.tryEvent(.dismissFlow)
}
navigationStackCoordinator.setSheetCoordinator(nil)
}
}
.store(in: &cancellables)
stackCoordinator.setRootCoordinator(coordinator)
navigationStackCoordinator.setSheetCoordinator(stackCoordinator) { [weak self] in
self?.stateMachine.tryEvent(.dismissReportRoomScreen)
}
}
// MARK: - Other flows // MARK: - Other flows
private func startChildFlow(for roomID: String, via: [String], entryPoint: RoomFlowCoordinatorEntryPoint) async { private func startChildFlow(for roomID: String, via: [String], entryPoint: RoomFlowCoordinatorEntryPoint) async {
@@ -1686,6 +1721,7 @@ private extension RoomFlowCoordinator {
case knockRequestsList(previousState: State) case knockRequestsList(previousState: State)
case mediaEventsTimeline(previousState: State) case mediaEventsTimeline(previousState: State)
case securityAndPrivacy(previousState: State) case securityAndPrivacy(previousState: State)
case reportRoom(previousState: State)
/// A child flow is in progress. /// A child flow is in progress.
case presentingChild(childRoomID: String, previousState: State) case presentingChild(childRoomID: String, previousState: State)
@@ -1772,5 +1808,8 @@ private extension RoomFlowCoordinator {
case presentSecurityAndPrivacyScreen case presentSecurityAndPrivacyScreen
case dismissSecurityAndPrivacyScreen case dismissSecurityAndPrivacyScreen
case presentReportRoomScreen
case dismissReportRoomScreen
} }
} }
@@ -172,7 +172,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
} }
case .roomDetails(let roomID): case .roomDetails(let roomID):
if stateMachine.state.selectedRoomID == roomID { if stateMachine.state.roomListSelectedRoomID == roomID {
roomFlowCoordinator?.handleAppRoute(appRoute, animated: animated) roomFlowCoordinator?.handleAppRoute(appRoute, animated: animated)
} else { } else {
stateMachine.processEvent(.selectRoom(roomID: roomID, via: [], entryPoint: .roomDetails), userInfo: .init(animated: animated)) stateMachine.processEvent(.selectRoom(roomID: roomID, via: [], entryPoint: .roomDetails), userInfo: .init(animated: animated))
@@ -245,8 +245,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
case (.initial, .start, .roomList): case (.initial, .start, .roomList):
presentHomeScreen() presentHomeScreen()
attemptStartingOnboarding() attemptStartingOnboarding()
case(.roomList(let selectedRoomID), .selectRoom(let roomID, let via, let entryPoint), .roomList): case(.roomList(let roomListSelectedRoomID), .selectRoom(let roomID, let via, let entryPoint), .roomList):
if selectedRoomID == roomID, if roomListSelectedRoomID == roomID,
!entryPoint.isEventID, // Don't reuse the existing room so the live timeline is hidden while the detached timeline is loading. !entryPoint.isEventID, // Don't reuse the existing room so the live timeline is hidden while the detached timeline is loading.
let roomFlowCoordinator { let roomFlowCoordinator {
let route: AppRoute = switch entryPoint { let route: AppRoute = switch entryPoint {
@@ -307,9 +307,14 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
case (.userProfileScreen, .dismissedUserProfileScreen, .roomList): case (.userProfileScreen, .dismissedUserProfileScreen, .roomList):
break break
case (.roomList(let selectedRoomID), .showShareExtensionRoomList, .shareExtensionRoomList(let sharePayload)): case (.roomList, .presentReportRoomScreen(let roomID), .reportRoomScreen):
Task { await self.presentReportRoom(for: roomID) }
case (.reportRoomScreen, .dismissedReportRoomScreen, .roomList):
break
case (.roomList(let roomListSelectedRoomID), .showShareExtensionRoomList, .shareExtensionRoomList(let sharePayload)):
Task { Task {
if selectedRoomID != nil { if roomListSelectedRoomID != nil {
self.clearRoute(animated: animated) self.clearRoute(animated: animated)
try? await Task.sleep(for: .seconds(1.5)) try? await Task.sleep(for: .seconds(1.5))
} }
@@ -326,8 +331,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
stateMachine.addTransitionHandler { [weak self] context in stateMachine.addTransitionHandler { [weak self] context in
switch context.toState { switch context.toState {
case .roomList(let selectedRoomID): case .roomList(let roomListSelectedRoomID):
self?.selectedRoomSubject.send(selectedRoomID) self?.selectedRoomSubject.send(roomListSelectedRoomID)
default: default:
break break
} }
@@ -496,9 +501,11 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
handleAppRoute(.room(roomID: roomID, via: []), animated: true) handleAppRoute(.room(roomID: roomID, via: []), animated: true)
case .presentRoomDetails(let roomID): case .presentRoomDetails(let roomID):
handleAppRoute(.roomDetails(roomID: roomID), animated: true) handleAppRoute(.roomDetails(roomID: roomID), animated: true)
case .presentReportRoom(let roomID):
stateMachine.processEvent(.presentReportRoomScreen(roomID: roomID))
case .roomLeft(let roomID): case .roomLeft(let roomID):
if case .roomList(selectedRoomID: let selectedRoomID) = stateMachine.state, if case .roomList(roomListSelectedRoomID: let roomListSelectedRoomID) = stateMachine.state,
selectedRoomID == roomID { roomListSelectedRoomID == roomID {
clearRoute(animated: true) clearRoute(animated: true)
} }
case .presentSettingsScreen: case .presentSettingsScreen:
@@ -528,6 +535,35 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
navigationRootCoordinator.setRootCoordinator(navigationSplitCoordinator) navigationRootCoordinator.setRootCoordinator(navigationSplitCoordinator)
} }
private func presentReportRoom(for roomID: String) async {
guard let roomProxyType = await userSession.clientProxy.roomForIdentifier(roomID),
case let .joined(roomProxy) = roomProxyType else {
MXLog.error("Failed to get room proxy for room: \(roomID)")
return
}
let navigationStackCoordinator = NavigationStackCoordinator()
let coordinator = ReportRoomScreenCoordinator(parameters: .init(roomProxy: roomProxy,
userIndicatorController: ServiceLocator.shared.userIndicatorController))
coordinator.actionsPublisher.sink { [weak self] action in
guard let self else { return }
switch action {
case .dismiss(let shouldLeaveRoom):
if shouldLeaveRoom,
case .roomList(let roomListSelectedRoomID) = stateMachine.state,
roomListSelectedRoomID == roomID {
clearRoute(animated: true)
}
navigationSplitCoordinator.setSheetCoordinator(nil)
}
}
.store(in: &cancellables)
navigationStackCoordinator.setRootCoordinator(coordinator)
navigationSplitCoordinator.setSheetCoordinator(navigationStackCoordinator) { [weak self] in
self?.stateMachine.processEvent(.dismissedReportRoomScreen)
}
}
private func runLogoutFlow() async { private func runLogoutFlow() async {
let secureBackupController = userSession.clientProxy.secureBackupController let secureBackupController = userSession.clientProxy.secureBackupController
@@ -15,49 +15,53 @@ class UserSessionFlowCoordinatorStateMachine {
/// The initial state, used before the coordinator starts /// The initial state, used before the coordinator starts
case initial case initial
/// Showing the home screen. The `selectedRoomID` represents the timeline shown on the detail panel (if any) /// Showing the home screen. The `roomListSelectedRoomID` represents the timeline shown on the detail panel (if any)
case roomList(selectedRoomID: String?) case roomList(roomListSelectedRoomID: String?)
/// Showing the feedback screen. /// Showing the feedback screen.
case feedbackScreen(selectedRoomID: String?) case feedbackScreen(roomListSelectedRoomID: String?)
/// Showing the settings screen /// Showing the settings screen
case settingsScreen(selectedRoomID: String?) case settingsScreen(roomListSelectedRoomID: String?)
/// Showing the recovery key screen. /// Showing the recovery key screen.
case recoveryKeyScreen(selectedRoomID: String?) case recoveryKeyScreen(roomListSelectedRoomID: String?)
/// Showing the encryption reset flow. /// Showing the encryption reset flow.
case encryptionResetFlow(selectedRoomID: String?) case encryptionResetFlow(roomListSelectedRoomID: String?)
/// Showing the start chat screen /// Showing the start chat screen
case startChatScreen(selectedRoomID: String?) case startChatScreen(roomListSelectedRoomID: String?)
/// Showing the logout flows /// Showing the logout flows
case logoutConfirmationScreen(selectedRoomID: String?) case logoutConfirmationScreen(roomListSelectedRoomID: String?)
/// Showing Room Directory Search screen /// Showing Room Directory Search screen
case roomDirectorySearchScreen(selectedRoomID: String?) case roomDirectorySearchScreen(roomListSelectedRoomID: String?)
/// Showing the user profile screen. This screen clears the navigation. /// Showing the user profile screen. This screen clears the navigation.
case userProfileScreen case userProfileScreen
/// Showing the report room screen, for the given room identrifier
case reportRoomScreen(roomListSelectedRoomID: String?)
case shareExtensionRoomList(sharePayload: ShareExtensionPayload) case shareExtensionRoomList(sharePayload: ShareExtensionPayload)
/// The selected room ID from the state if available. /// The selected room ID from the state if available.
var selectedRoomID: String? { var roomListSelectedRoomID: String? {
switch self { switch self {
case .initial, .userProfileScreen, .shareExtensionRoomList: case .initial, .userProfileScreen, .shareExtensionRoomList:
nil nil
case .roomList(let selectedRoomID), case .roomList(let roomListSelectedRoomID),
.feedbackScreen(let selectedRoomID), .feedbackScreen(let roomListSelectedRoomID),
.settingsScreen(let selectedRoomID), .settingsScreen(let roomListSelectedRoomID),
.recoveryKeyScreen(let selectedRoomID), .recoveryKeyScreen(let roomListSelectedRoomID),
.encryptionResetFlow(let selectedRoomID), .encryptionResetFlow(let roomListSelectedRoomID),
.startChatScreen(let selectedRoomID), .startChatScreen(let roomListSelectedRoomID),
.logoutConfirmationScreen(let selectedRoomID), .logoutConfirmationScreen(let roomListSelectedRoomID),
.roomDirectorySearchScreen(let selectedRoomID): .roomDirectorySearchScreen(let roomListSelectedRoomID),
selectedRoomID .reportRoomScreen(let roomListSelectedRoomID):
roomListSelectedRoomID
} }
} }
} }
@@ -121,6 +125,9 @@ class UserSessionFlowCoordinatorStateMachine {
case showShareExtensionRoomList(sharePayload: ShareExtensionPayload) case showShareExtensionRoomList(sharePayload: ShareExtensionPayload)
case dismissedShareExtensionRoomList case dismissedShareExtensionRoomList
case presentReportRoomScreen(roomID: String)
case dismissedReportRoomScreen
} }
private let stateMachine: StateMachine<State, Event> private let stateMachine: StateMachine<State, Event>
@@ -140,59 +147,64 @@ class UserSessionFlowCoordinatorStateMachine {
} }
private func configure() { private func configure() {
stateMachine.addRoutes(event: .start, transitions: [.initial => .roomList(selectedRoomID: nil)]) stateMachine.addRoutes(event: .start, transitions: [.initial => .roomList(roomListSelectedRoomID: nil)])
stateMachine.addRouteMapping { event, fromState, _ in stateMachine.addRouteMapping { event, fromState, _ in
switch (fromState, event) { switch (fromState, event) {
case (.roomList, .selectRoom(let roomID, _, _)): case (.roomList, .selectRoom(let roomID, _, _)):
return .roomList(selectedRoomID: roomID) return .roomList(roomListSelectedRoomID: roomID)
case (.roomList, .deselectRoom): case (.roomList, .deselectRoom):
return .roomList(selectedRoomID: nil) return .roomList(roomListSelectedRoomID: nil)
case (.roomList(let selectedRoomID), .showSettingsScreen): case (.roomList(let roomListSelectedRoomID), .showSettingsScreen):
return .settingsScreen(selectedRoomID: selectedRoomID) return .settingsScreen(roomListSelectedRoomID: roomListSelectedRoomID)
case (.settingsScreen(let selectedRoomID), .dismissedSettingsScreen): case (.settingsScreen(let roomListSelectedRoomID), .dismissedSettingsScreen):
return .roomList(selectedRoomID: selectedRoomID) return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
case (.roomList(let selectedRoomID), .feedbackScreen): case (.roomList(let roomListSelectedRoomID), .feedbackScreen):
return .feedbackScreen(selectedRoomID: selectedRoomID) return .feedbackScreen(roomListSelectedRoomID: roomListSelectedRoomID)
case (.feedbackScreen(let selectedRoomID), .dismissedFeedbackScreen): case (.feedbackScreen(let roomListSelectedRoomID), .dismissedFeedbackScreen):
return .roomList(selectedRoomID: selectedRoomID) return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
case (.roomList(let selectedRoomID), .showRecoveryKeyScreen): case (.roomList(let roomListSelectedRoomID), .showRecoveryKeyScreen):
return .recoveryKeyScreen(selectedRoomID: selectedRoomID) return .recoveryKeyScreen(roomListSelectedRoomID: roomListSelectedRoomID)
case (.recoveryKeyScreen(let selectedRoomID), .dismissedRecoveryKeyScreen): case (.recoveryKeyScreen(let roomListSelectedRoomID), .dismissedRecoveryKeyScreen):
return .roomList(selectedRoomID: selectedRoomID) return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
case (.roomList(let selectedRoomID), .startEncryptionResetFlow): case (.roomList(let roomListSelectedRoomID), .startEncryptionResetFlow):
return .encryptionResetFlow(selectedRoomID: selectedRoomID) return .encryptionResetFlow(roomListSelectedRoomID: roomListSelectedRoomID)
case (.encryptionResetFlow(let selectedRoomID), .finishedEncryptionResetFlow): case (.encryptionResetFlow(let roomListSelectedRoomID), .finishedEncryptionResetFlow):
return .roomList(selectedRoomID: selectedRoomID) return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
case (.roomList(let selectedRoomID), .showStartChatScreen): case (.roomList(let roomListSelectedRoomID), .showStartChatScreen):
return .startChatScreen(selectedRoomID: selectedRoomID) return .startChatScreen(roomListSelectedRoomID: roomListSelectedRoomID)
case (.startChatScreen(let selectedRoomID), .dismissedStartChatScreen): case (.startChatScreen(let roomListSelectedRoomID), .dismissedStartChatScreen):
return .roomList(selectedRoomID: selectedRoomID) return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
case (.roomList(let selectedRoomID), .showLogoutConfirmationScreen): case (.roomList(let roomListSelectedRoomID), .showLogoutConfirmationScreen):
return .logoutConfirmationScreen(selectedRoomID: selectedRoomID) return .logoutConfirmationScreen(roomListSelectedRoomID: roomListSelectedRoomID)
case (.logoutConfirmationScreen(let selectedRoomID), .dismissedLogoutConfirmationScreen): case (.logoutConfirmationScreen(let roomListSelectedRoomID), .dismissedLogoutConfirmationScreen):
return .roomList(selectedRoomID: selectedRoomID) return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
case (.roomList(let selectedRoomID), .showRoomDirectorySearchScreen): case (.roomList(let roomListSelectedRoomID), .showRoomDirectorySearchScreen):
return .roomDirectorySearchScreen(selectedRoomID: selectedRoomID) return .roomDirectorySearchScreen(roomListSelectedRoomID: roomListSelectedRoomID)
case (.roomDirectorySearchScreen(let selectedRoomID), .dismissedRoomDirectorySearchScreen): case (.roomDirectorySearchScreen(let roomListSelectedRoomID), .dismissedRoomDirectorySearchScreen):
return .roomList(selectedRoomID: selectedRoomID) return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
case (_, .showUserProfileScreen): case (_, .showUserProfileScreen):
return .userProfileScreen return .userProfileScreen
case (.userProfileScreen, .dismissedUserProfileScreen): case (.userProfileScreen, .dismissedUserProfileScreen):
return .roomList(selectedRoomID: nil) return .roomList(roomListSelectedRoomID: nil)
case (.roomList, .showShareExtensionRoomList(let sharePayload)): case (.roomList, .showShareExtensionRoomList(let sharePayload)):
return .shareExtensionRoomList(sharePayload: sharePayload) return .shareExtensionRoomList(sharePayload: sharePayload)
case (.shareExtensionRoomList, .dismissedShareExtensionRoomList): case (.shareExtensionRoomList, .dismissedShareExtensionRoomList):
return .roomList(selectedRoomID: nil) return .roomList(roomListSelectedRoomID: nil)
case (.roomList(let roomListSelectedRoomID), .presentReportRoomScreen):
return .reportRoomScreen(roomListSelectedRoomID: roomListSelectedRoomID)
case (.reportRoomScreen(let roomListSelectedRoomID), .dismissedReportRoomScreen):
return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
default: default:
return nil return nil
@@ -231,8 +243,8 @@ class UserSessionFlowCoordinatorStateMachine {
/// Flag indicating the machine is displaying room screen with given room identifier /// Flag indicating the machine is displaying room screen with given room identifier
func isDisplayingRoomScreen(withRoomID roomID: String) -> Bool { func isDisplayingRoomScreen(withRoomID roomID: String) -> Bool {
switch stateMachine.state { switch stateMachine.state {
case .roomList(let selectedRoomID): case .roomList(let roomListSelectedRoomID):
return roomID == selectedRoomID return roomID == roomListSelectedRoomID
default: default:
return false return false
} }
@@ -6689,6 +6689,76 @@ class JoinedRoomProxyMock: JoinedRoomProxyProtocol, @unchecked Sendable {
return reportContentReasonReturnValue return reportContentReasonReturnValue
} }
} }
//MARK: - reportRoom
var reportRoomReasonUnderlyingCallsCount = 0
var reportRoomReasonCallsCount: Int {
get {
if Thread.isMainThread {
return reportRoomReasonUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = reportRoomReasonUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
reportRoomReasonUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
reportRoomReasonUnderlyingCallsCount = newValue
}
}
}
}
var reportRoomReasonCalled: Bool {
return reportRoomReasonCallsCount > 0
}
var reportRoomReasonReceivedReason: String?
var reportRoomReasonReceivedInvocations: [String?] = []
var reportRoomReasonUnderlyingReturnValue: Result<Void, RoomProxyError>!
var reportRoomReasonReturnValue: Result<Void, RoomProxyError>! {
get {
if Thread.isMainThread {
return reportRoomReasonUnderlyingReturnValue
} else {
var returnValue: Result<Void, RoomProxyError>? = nil
DispatchQueue.main.sync {
returnValue = reportRoomReasonUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
reportRoomReasonUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
reportRoomReasonUnderlyingReturnValue = newValue
}
}
}
}
var reportRoomReasonClosure: ((String?) async -> Result<Void, RoomProxyError>)?
func reportRoom(reason: String?) async -> Result<Void, RoomProxyError> {
reportRoomReasonCallsCount += 1
reportRoomReasonReceivedReason = reason
DispatchQueue.main.async {
self.reportRoomReasonReceivedInvocations.append(reason)
}
if let reportRoomReasonClosure = reportRoomReasonClosure {
return await reportRoomReasonClosure(reason)
} else {
return reportRoomReasonReturnValue
}
}
//MARK: - leaveRoom //MARK: - leaveRoom
var leaveRoomUnderlyingCallsCount = 0 var leaveRoomUnderlyingCallsCount = 0
@@ -17,6 +17,7 @@ struct HomeScreenCoordinatorParameters {
enum HomeScreenCoordinatorAction { enum HomeScreenCoordinatorAction {
case presentRoom(roomIdentifier: String) case presentRoom(roomIdentifier: String)
case presentRoomDetails(roomIdentifier: String) case presentRoomDetails(roomIdentifier: String)
case presentReportRoom(roomIdentifier: String)
case roomLeft(roomIdentifier: String) case roomLeft(roomIdentifier: String)
case presentSettingsScreen case presentSettingsScreen
case presentFeedbackScreen case presentFeedbackScreen
@@ -58,6 +59,8 @@ final class HomeScreenCoordinator: CoordinatorProtocol {
actionsSubject.send(.presentRoom(roomIdentifier: roomIdentifier)) actionsSubject.send(.presentRoom(roomIdentifier: roomIdentifier))
case .presentRoomDetails(roomIdentifier: let roomIdentifier): case .presentRoomDetails(roomIdentifier: let roomIdentifier):
actionsSubject.send(.presentRoomDetails(roomIdentifier: roomIdentifier)) actionsSubject.send(.presentRoomDetails(roomIdentifier: roomIdentifier))
case .presentReportRoom(let roomIdentifier):
actionsSubject.send(.presentReportRoom(roomIdentifier: roomIdentifier))
case .roomLeft(roomIdentifier: let roomIdentifier): case .roomLeft(roomIdentifier: let roomIdentifier):
actionsSubject.send(.roomLeft(roomIdentifier: roomIdentifier)) actionsSubject.send(.roomLeft(roomIdentifier: roomIdentifier))
case .presentFeedbackScreen: case .presentFeedbackScreen:
@@ -12,6 +12,7 @@ import UIKit
enum HomeScreenViewModelAction: Equatable { enum HomeScreenViewModelAction: Equatable {
case presentRoom(roomIdentifier: String) case presentRoom(roomIdentifier: String)
case presentRoomDetails(roomIdentifier: String) case presentRoomDetails(roomIdentifier: String)
case presentReportRoom(roomIdentifier: String)
case roomLeft(roomIdentifier: String) case roomLeft(roomIdentifier: String)
case presentSecureBackupSettings case presentSecureBackupSettings
case presentRecoveryKeyScreen case presentRecoveryKeyScreen
@@ -29,6 +30,7 @@ enum HomeScreenViewAction {
case showRoomDetails(roomIdentifier: String) case showRoomDetails(roomIdentifier: String)
case leaveRoom(roomIdentifier: String) case leaveRoom(roomIdentifier: String)
case confirmLeaveRoom(roomIdentifier: String) case confirmLeaveRoom(roomIdentifier: String)
case reportRoom(roomIdentifier: String)
case showSettings case showSettings
case startChat case startChat
case setupRecovery case setupRecovery
@@ -98,6 +100,10 @@ struct HomeScreenViewState: BindableState {
var selectedRoomID: String? var selectedRoomID: String?
var hideInviteAvatars = false
var reportRoomEnabled = false
var visibleRooms: [HomeScreenRoom] { var visibleRooms: [HomeScreenRoom] {
if roomListMode == .skeletons { if roomListMode == .skeletons {
return placeholderRooms return placeholderRooms
@@ -105,9 +111,7 @@ struct HomeScreenViewState: BindableState {
return rooms return rooms
} }
var hideInviteAvatars = false
var bindings = HomeScreenViewStateBindings() var bindings = HomeScreenViewStateBindings()
var placeholderRooms: [HomeScreenRoom] { var placeholderRooms: [HomeScreenRoom] {
@@ -105,6 +105,10 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
.weakAssign(to: \.state.hideInviteAvatars, on: self) .weakAssign(to: \.state.hideInviteAvatars, on: self)
.store(in: &cancellables) .store(in: &cancellables)
appSettings.$reportRoomEnabled
.weakAssign(to: \.state.reportRoomEnabled, on: self)
.store(in: &cancellables)
let isSearchFieldFocused = context.$viewState.map(\.bindings.isSearchFieldFocused) let isSearchFieldFocused = context.$viewState.map(\.bindings.isSearchFieldFocused)
let searchQuery = context.$viewState.map(\.bindings.searchQuery) let searchQuery = context.$viewState.map(\.bindings.searchQuery)
let activeFilters = context.$viewState.map(\.bindings.filtersState.activeFilters) let activeFilters = context.$viewState.map(\.bindings.filtersState.activeFilters)
@@ -136,12 +140,14 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
switch viewAction { switch viewAction {
case .selectRoom(let roomIdentifier): case .selectRoom(let roomIdentifier):
actionsSubject.send(.presentRoom(roomIdentifier: roomIdentifier)) actionsSubject.send(.presentRoom(roomIdentifier: roomIdentifier))
case .showRoomDetails(roomIdentifier: let roomIdentifier): case .showRoomDetails(let roomIdentifier):
actionsSubject.send(.presentRoomDetails(roomIdentifier: roomIdentifier)) actionsSubject.send(.presentRoomDetails(roomIdentifier: roomIdentifier))
case .leaveRoom(roomIdentifier: let roomIdentifier): case .leaveRoom(let roomIdentifier):
startLeaveRoomProcess(roomID: roomIdentifier) startLeaveRoomProcess(roomID: roomIdentifier)
case .confirmLeaveRoom(roomIdentifier: let roomIdentifier): case .confirmLeaveRoom(let roomIdentifier):
Task { await leaveRoom(roomID: roomIdentifier) } Task { await leaveRoom(roomID: roomIdentifier) }
case .reportRoom(let roomIdentifier):
actionsSubject.send(.presentReportRoom(roomIdentifier: roomIdentifier))
case .showSettings: case .showSettings:
actionsSubject.send(.presentSettingsScreen) actionsSubject.send(.presentSettingsScreen)
case .setupRecovery: case .setupRecovery:
@@ -69,6 +69,14 @@ struct HomeScreenRoomList: View {
Label(L10n.commonSettings, icon: \.settings) Label(L10n.commonSettings, icon: \.settings)
} }
if context.viewState.reportRoomEnabled {
Button(role: .destructive) {
context.send(viewAction: .reportRoom(roomIdentifier: room.id))
} label: {
Label(L10n.actionReportRoom, icon: \.chatProblem)
}
}
Button(role: .destructive) { Button(role: .destructive) {
context.send(viewAction: .leaveRoom(roomIdentifier: room.id)) context.send(viewAction: .leaveRoom(roomIdentifier: room.id))
} label: { } label: {
@@ -0,0 +1,54 @@
//
// Copyright 2022-2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
//
// periphery:ignore:all - this is just a reportRoom remove this comment once generating the final file
import Combine
import SwiftUI
struct ReportRoomScreenCoordinatorParameters {
let roomProxy: JoinedRoomProxyProtocol
let userIndicatorController: UserIndicatorControllerProtocol
}
enum ReportRoomScreenCoordinatorAction {
case dismiss(shouldLeaveRoom: Bool)
}
final class ReportRoomScreenCoordinator: CoordinatorProtocol {
private let parameters: ReportRoomScreenCoordinatorParameters
private let viewModel: ReportRoomScreenViewModelProtocol
private var cancellables = Set<AnyCancellable>()
private let actionsSubject: PassthroughSubject<ReportRoomScreenCoordinatorAction, Never> = .init()
var actionsPublisher: AnyPublisher<ReportRoomScreenCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(parameters: ReportRoomScreenCoordinatorParameters) {
self.parameters = parameters
viewModel = ReportRoomScreenViewModel(roomProxy: parameters.roomProxy,
userIndicatorController: parameters.userIndicatorController)
}
func start() {
viewModel.actionsPublisher.sink { [weak self] action in
guard let self else { return }
switch action {
case .dismiss(let shouldLeaveRoom):
actionsSubject.send(.dismiss(shouldLeaveRoom: shouldLeaveRoom))
}
}
.store(in: &cancellables)
}
func toPresentable() -> AnyView {
AnyView(ReportRoomScreen(context: viewModel.context))
}
}
@@ -0,0 +1,31 @@
//
// Copyright 2022-2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
//
import Foundation
enum ReportRoomScreenViewModelAction: Equatable {
case dismiss(shouldLeaveRoom: Bool)
}
struct ReportRoomScreenViewState: BindableState {
var bindings = ReportRoomScreenViewStateBindings()
}
struct ReportRoomScreenViewStateBindings {
var reason = ""
var shouldLeaveRoom = false
var alert: AlertInfo<ReportRoomScreenAlertType>?
}
enum ReportRoomScreenViewAction {
case report
case dismiss
}
enum ReportRoomScreenAlertType {
case leaveRoomFailed
}
@@ -0,0 +1,91 @@
//
// Copyright 2022-2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
//
import Combine
import SwiftUI
typealias ReportRoomScreenViewModelType = StateStoreViewModel<ReportRoomScreenViewState, ReportRoomScreenViewAction>
class ReportRoomScreenViewModel: ReportRoomScreenViewModelType, ReportRoomScreenViewModelProtocol {
let roomProxy: JoinedRoomProxyProtocol
let userIndicatorController: UserIndicatorControllerProtocol
private let actionsSubject: PassthroughSubject<ReportRoomScreenViewModelAction, Never> = .init()
var actionsPublisher: AnyPublisher<ReportRoomScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(roomProxy: JoinedRoomProxyProtocol, userIndicatorController: UserIndicatorControllerProtocol) {
self.roomProxy = roomProxy
self.userIndicatorController = userIndicatorController
super.init(initialViewState: ReportRoomScreenViewState())
}
// MARK: - Public
override func process(viewAction: ReportRoomScreenViewAction) {
switch viewAction {
case .report:
Task { await report() }
case .dismiss:
actionsSubject.send(.dismiss(shouldLeaveRoom: false))
}
}
private func report() async {
showLoadingIndicator()
let result = await roomProxy.reportRoom(reason: state.bindings.reason.isBlank ? nil : state.bindings.reason)
switch result {
case .success:
if state.bindings.shouldLeaveRoom {
await leaveRoom(showLoading: false)
} else {
hideLoadingIndicator()
userIndicatorController.submitIndicator(.init(title: L10n.dialogRoomReported, iconName: "checkmark"))
actionsSubject.send(.dismiss(shouldLeaveRoom: false))
}
case .failure:
hideLoadingIndicator()
userIndicatorController.submitIndicator(.init(title: L10n.errorUnknown))
}
}
private func leaveRoom(showLoading: Bool) async {
if showLoading {
showLoadingIndicator()
}
let result = await roomProxy.leaveRoom()
hideLoadingIndicator()
switch result {
case .success:
userIndicatorController.submitIndicator(.init(title: L10n.dialogRoomReportedAndLeft, iconName: "checkmark"))
actionsSubject.send(.dismiss(shouldLeaveRoom: true))
case .failure:
state.bindings.alert = .init(id: .leaveRoomFailed,
title: L10n.screenReportRoomLeaveFailedAlertTitle,
message: L10n.screenReportRoomLeaveFailedAlertMessage,
primaryButton: .init(title: L10n.actionDismiss, role: .cancel) { [weak self] in self?.actionsSubject.send(.dismiss(shouldLeaveRoom: false)) },
secondaryButton: .init(title: L10n.actionRetry) { [weak self] in Task { await self?.leaveRoom(showLoading: true) } })
}
}
private static let loadingIndicatorIdentifier = "\(BugReportScreenCoordinator.self)-Loading"
private func showLoadingIndicator() {
userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,
type: .modal(progress: .indeterminate, interactiveDismissDisabled: true, allowsInteraction: false),
title: L10n.commonLoading,
persistent: true))
}
private func hideLoadingIndicator() {
userIndicatorController.retractIndicatorWithId(Self.loadingIndicatorIdentifier)
}
}
@@ -0,0 +1,14 @@
//
// Copyright 2022-2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
//
import Combine
@MainActor
protocol ReportRoomScreenViewModelProtocol {
var actionsPublisher: AnyPublisher<ReportRoomScreenViewModelAction, Never> { get }
var context: ReportRoomScreenViewModelType.Context { get }
}
@@ -0,0 +1,70 @@
//
// Copyright 2022-2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
//
import Compound
import SwiftUI
struct ReportRoomScreen: View {
@ObservedObject var context: ReportRoomScreenViewModel.Context
var body: some View {
Form {
reasonSection
leaveRoomSection
}
.compoundList()
.navigationTitle(L10n.screenReportRoomTitle)
.navigationBarTitleDisplayMode(.inline)
.toolbar { toolbar }
.alert(item: $context.alert)
}
private var reasonSection: some View {
Section {
ListRow(label: .plain(title: L10n.screenReportRoomReasonPlaceholder),
kind: .textField(text: $context.reason, axis: .vertical))
.lineLimit(4, reservesSpace: true)
} footer: {
Text(L10n.screenReportRoomReasonFooter)
.compoundListSectionFooter()
}
}
private var leaveRoomSection: some View {
Section {
ListRow(label: .plain(title: L10n.actionLeaveRoom),
kind: .toggle($context.shouldLeaveRoom))
}
}
@ToolbarContentBuilder
private var toolbar: some ToolbarContent {
ToolbarItem(placement: .cancellationAction) {
Button(L10n.actionCancel) {
context.send(viewAction: .dismiss)
}
}
ToolbarItem(placement: .confirmationAction) {
Button(L10n.actionReport) {
context.send(viewAction: .report)
}
}
}
}
// MARK: - Previews
struct ReportRoomScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = ReportRoomScreenViewModel(roomProxy: JoinedRoomProxyMock(.init()),
userIndicatorController: UserIndicatorControllerMock())
static var previews: some View {
NavigationStack {
ReportRoomScreen(context: viewModel.context)
}
}
}
@@ -33,6 +33,7 @@ enum RoomDetailsScreenCoordinatorAction {
case presentMediaEventsTimeline case presentMediaEventsTimeline
case presentKnockingRequestsListScreen case presentKnockingRequestsListScreen
case presentSecurityAndPrivacyScreen case presentSecurityAndPrivacyScreen
case presentReportRoomScreen
} }
final class RoomDetailsScreenCoordinator: CoordinatorProtocol { final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
@@ -91,6 +92,8 @@ final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
actionsSubject.send(.presentSecurityAndPrivacyScreen) actionsSubject.send(.presentSecurityAndPrivacyScreen)
case .requestRecipientDetailsPresentation(let userID): case .requestRecipientDetailsPresentation(let userID):
actionsSubject.send(.presentRecipientDetails(userID: userID)) actionsSubject.send(.presentRecipientDetails(userID: userID))
case .displayReportRoom:
actionsSubject.send(.presentReportRoomScreen)
} }
} }
.store(in: &cancellables) .store(in: &cancellables)
@@ -26,6 +26,7 @@ enum RoomDetailsScreenViewModelAction: Equatable {
case displayMediaEventsTimeline case displayMediaEventsTimeline
case displayKnockingRequests case displayKnockingRequests
case displaySecurityAndPrivacy case displaySecurityAndPrivacy
case displayReportRoom
} }
// MARK: View // MARK: View
@@ -64,6 +65,8 @@ struct RoomDetailsScreenViewState: BindableState {
var isKnockableRoom = false var isKnockableRoom = false
var knockRequestsCount = 0 var knockRequestsCount = 0
var reportRoomEnabled = false
var canSeeKnockingRequests: Bool { var canSeeKnockingRequests: Bool {
knockingEnabled && dmRecipientInfo == nil && isKnockableRoom && (canInviteUsers || canKickUsers || canBanUsers) knockingEnabled && dmRecipientInfo == nil && isKnockableRoom && (canInviteUsers || canKickUsers || canBanUsers)
} }
@@ -218,6 +221,7 @@ enum RoomDetailsScreenViewAction {
case processTapPinnedEvents case processTapPinnedEvents
case processTapMediaEvents case processTapMediaEvents
case processTapRequestsToJoin case processTapRequestsToJoin
case processTapReport
} }
enum RoomDetailsScreenViewShortcut { enum RoomDetailsScreenViewShortcut {
@@ -78,6 +78,10 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
.weakAssign(to: \.state.knockingEnabled, on: self) .weakAssign(to: \.state.knockingEnabled, on: self)
.store(in: &cancellables) .store(in: &cancellables)
appSettings.$reportRoomEnabled
.weakAssign(to: \.state.reportRoomEnabled, on: self)
.store(in: &cancellables)
appMediator.networkMonitor.reachabilityPublisher appMediator.networkMonitor.reachabilityPublisher
.filter { $0 == .reachable } .filter { $0 == .reachable }
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
@@ -174,6 +178,8 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
return return
} }
actionsSubject.send(.requestRecipientDetailsPresentation(userID: userID)) actionsSubject.send(.requestRecipientDetailsPresentation(userID: userID))
case .processTapReport:
actionsSubject.send(.displayReportRoom)
} }
} }
@@ -275,9 +275,19 @@ struct RoomDetailsScreen: View {
private var leaveRoomTitle: String { private var leaveRoomTitle: String {
context.viewState.dmRecipientInfo == nil ? L10n.screenRoomDetailsLeaveRoomTitle : L10n.screenRoomDetailsLeaveConversationTitle context.viewState.dmRecipientInfo == nil ? L10n.screenRoomDetailsLeaveRoomTitle : L10n.screenRoomDetailsLeaveConversationTitle
} }
private var reportRoomTitle: String {
context.viewState.dmRecipientInfo == nil ? L10n.actionReportRoom : L10n.actionReport
}
private var leaveRoomSection: some View { private var leaveRoomSection: some View {
Section { Section {
if context.viewState.reportRoomEnabled {
ListRow(label: .action(title: reportRoomTitle,
icon: \.chatProblem,
role: .destructive),
kind: .button { context.send(viewAction: .processTapReport) })
}
ListRow(label: .action(title: leaveRoomTitle, ListRow(label: .action(title: leaveRoomTitle,
icon: \.leave, icon: \.leave,
role: .destructive), role: .destructive),
@@ -53,7 +53,7 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
case .kickMember(let member): case .kickMember(let member):
var reason: String? var reason: String?
let binding: Binding<String> = .init(get: { reason ?? "" }, let binding: Binding<String> = .init(get: { reason ?? "" },
set: { reason = $0.isEmpty ? nil : $0 }) set: { reason = $0.isBlank ? nil : $0 })
state.bindings.alertInfo = .init(id: .kickConfirmation, state.bindings.alertInfo = .init(id: .kickConfirmation,
title: L10n.screenRoomMemberListKickMemberConfirmationTitle, title: L10n.screenRoomMemberListKickMemberConfirmationTitle,
message: L10n.screenRoomMemberListKickMemberConfirmationDescription, message: L10n.screenRoomMemberListKickMemberConfirmationDescription,
@@ -66,7 +66,7 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
case .banMember(let member): case .banMember(let member):
var reason: String? var reason: String?
let binding: Binding<String> = .init(get: { reason ?? "" }, let binding: Binding<String> = .init(get: { reason ?? "" },
set: { reason = $0.isEmpty ? nil : $0 }) set: { reason = $0.isBlank ? nil : $0 })
state.bindings.alertInfo = .init(id: .banConfirmation, state.bindings.alertInfo = .init(id: .banConfirmation,
title: L10n.screenRoomMemberListBanMemberConfirmationTitle, title: L10n.screenRoomMemberListBanMemberConfirmationTitle,
message: L10n.screenRoomMemberListBanMemberConfirmationDescription, message: L10n.screenRoomMemberListBanMemberConfirmationDescription,
@@ -44,6 +44,7 @@ protocol DeveloperOptionsProtocol: AnyObject {
var enableOnlySignedDeviceIsolationMode: Bool { get set } var enableOnlySignedDeviceIsolationMode: Bool { get set }
var elementCallBaseURLOverride: URL? { get set } var elementCallBaseURLOverride: URL? { get set }
var knockingEnabled: Bool { get set } var knockingEnabled: Bool { get set }
var reportRoomEnabled: Bool { get set }
} }
extension AppSettings: DeveloperOptionsProtocol { } extension AppSettings: DeveloperOptionsProtocol { }
@@ -62,6 +62,13 @@ struct DeveloperOptionsScreen: View {
} footer: { } footer: {
Text("This setting controls how end-to-end encryption (E2EE) keys are exchanged. Enabling it will prevent the inclusion of devices that have not been explicitly verified by their owners.") Text("This setting controls how end-to-end encryption (E2EE) keys are exchanged. Enabling it will prevent the inclusion of devices that have not been explicitly verified by their owners.")
} }
Section("Reporting") {
Toggle(isOn: $context.reportRoomEnabled) {
Text("Report rooms")
Text("Report API might not work properly")
}
}
Section { Section {
TextField("Leave empty to use EC locally", text: $elementCallURLOverrideString) TextField("Leave empty to use EC locally", text: $elementCallURLOverrideString)
@@ -255,6 +255,16 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
} }
} }
func reportRoom(reason: String?) async -> Result<Void, RoomProxyError> {
do {
try await room.reportRoom(reason: reason)
return .success(())
} catch {
MXLog.error("Failed reporting room: \(id) with error: \(error)")
return .failure(.sdkError(error))
}
}
func updateMembers() async { func updateMembers() async {
// We always update members first using the no sync API in case internet is not readily available // We always update members first using the no sync API in case internet is not readily available
// To get the members stored on disk first, this API call is very fast. // To get the members stored on disk first, this API call is very fast.
@@ -92,6 +92,8 @@ protocol JoinedRoomProxyProtocol: RoomProxyProtocol {
func redact(_ eventID: String) async -> Result<Void, RoomProxyError> func redact(_ eventID: String) async -> Result<Void, RoomProxyError>
func reportContent(_ eventID: String, reason: String?) async -> Result<Void, RoomProxyError> func reportContent(_ eventID: String, reason: String?) async -> Result<Void, RoomProxyError>
func reportRoom(reason: String?) async -> Result<Void, RoomProxyError>
func leaveRoom() async -> Result<Void, RoomProxyError> func leaveRoom() async -> Result<Void, RoomProxyError>
@@ -593,6 +593,12 @@ extension PreviewTests {
} }
} }
func testReportRoomScreen() async throws {
for (index, preview) in ReportRoomScreen_Previews._allPreviews.enumerated() {
try await assertSnapshots(matching: preview, step: index)
}
}
func testResolveVerifiedUserSendFailureScreen() async throws { func testResolveVerifiedUserSendFailureScreen() async throws {
for (index, preview) in ResolveVerifiedUserSendFailureScreen_Previews._allPreviews.enumerated() { for (index, preview) in ResolveVerifiedUserSendFailureScreen_Previews._allPreviews.enumerated() {
try await assertSnapshots(matching: preview, step: index) try await assertSnapshots(matching: preview, step: index)
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ac131e760c33abc71ff2561532486148184b2145ab8908fc93c8f59fd52a63bd
size 111036
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ce3deae0c993d297780b9983f7864252b38ec5eb139b82c34eac5f66c344f30c
size 124615
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:71fabe839f7069969d2064f39430d6044ae176b057d355f26871e86f563b4bf1
size 63941
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1277567cb86dde2a5d0af84a0c0ec71405fea0a094a5c246757220316662e6f9
size 83244
@@ -0,0 +1,104 @@
//
// Copyright 2022-2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
//
import XCTest
@testable import ElementX
@MainActor
class ReportRoomScreenViewModelTests: XCTestCase {
var viewModel: ReportRoomScreenViewModelProtocol!
var roomProxy: JoinedRoomProxyMock!
var context: ReportRoomScreenViewModelType.Context {
viewModel.context
}
override func setUp() {
roomProxy = JoinedRoomProxyMock(.init())
viewModel = ReportRoomScreenViewModel(roomProxy: roomProxy, userIndicatorController: UserIndicatorControllerMock())
}
func testInitialState() {
XCTAssertTrue(context.viewState.bindings.reason.isEmpty)
XCTAssertFalse(context.viewState.bindings.shouldLeaveRoom)
}
func testReportSuccess() async throws {
let reason = "Spam"
let expectation = XCTestExpectation(description: "Report success")
roomProxy.reportRoomReasonClosure = { reasonArgument in
defer { expectation.fulfill() }
XCTAssertEqual(reasonArgument, reason)
return .success(())
}
let deferred = deferFulfillment(viewModel.actionsPublisher) { action in
action == .dismiss(shouldLeaveRoom: false)
}
context.reason = reason
context.send(viewAction: .report)
try await deferred.fulfill()
await fulfillment(of: [expectation])
}
func testReportAndLeaveSuccess() async throws {
let reason = "Spam"
let reportExpectation = XCTestExpectation(description: "Report success")
roomProxy.reportRoomReasonClosure = { reasonArgument in
defer { reportExpectation.fulfill() }
XCTAssertEqual(reasonArgument, reason)
return .success(())
}
let leaveExpectation = XCTestExpectation(description: "Leave success")
roomProxy.leaveRoomClosure = {
defer { leaveExpectation.fulfill() }
return .success(())
}
let deferred = deferFulfillment(viewModel.actionsPublisher) { action in
action == .dismiss(shouldLeaveRoom: true)
}
context.reason = reason
context.shouldLeaveRoom = true
context.send(viewAction: .report)
await fulfillment(of: [reportExpectation, leaveExpectation])
try await deferred.fulfill()
}
func testReportSuccessLeaveFails() async throws {
let reason = "Spam"
let reportExpectation = XCTestExpectation(description: "Report success")
roomProxy.reportRoomReasonClosure = { reasonArgument in
defer { reportExpectation.fulfill() }
XCTAssertEqual(reasonArgument, reason)
return .success(())
}
let leaveExpectation = XCTestExpectation(description: "Leave fails")
roomProxy.leaveRoomClosure = {
defer { leaveExpectation.fulfill() }
return .failure(.eventNotFound)
}
let deferred = deferFulfillment(context.$viewState) { state in
state.bindings.alert != nil
}
context.reason = reason
context.shouldLeaveRoom = true
context.send(viewAction: .report)
await fulfillment(of: [reportExpectation, leaveExpectation])
try await deferred.fulfill()
}
}
@@ -46,29 +46,29 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
notificationManager: notificationManager, notificationManager: notificationManager,
isNewLogin: false) isNewLogin: false)
let deferred = deferFulfillment(userSessionFlowCoordinator.statePublisher) { $0 == .roomList(selectedRoomID: nil) } let deferred = deferFulfillment(userSessionFlowCoordinator.statePublisher) { $0 == .roomList(roomListSelectedRoomID: nil) }
userSessionFlowCoordinator.start() userSessionFlowCoordinator.start()
try await deferred.fulfill() try await deferred.fulfill()
} }
func testRoomPresentation() async throws { func testRoomPresentation() async throws {
try await process(route: .room(roomID: "1", via: []), expectedState: .roomList(selectedRoomID: "1")) try await process(route: .room(roomID: "1", via: []), expectedState: .roomList(roomListSelectedRoomID: "1"))
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator) XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
XCTAssertNotNil(detailCoordinator) XCTAssertNotNil(detailCoordinator)
try await process(route: .roomList, expectedState: .roomList(selectedRoomID: nil)) try await process(route: .roomList, expectedState: .roomList(roomListSelectedRoomID: nil))
XCTAssertNil(detailNavigationStack?.rootCoordinator) XCTAssertNil(detailNavigationStack?.rootCoordinator)
XCTAssertNil(detailCoordinator) XCTAssertNil(detailCoordinator)
try await process(route: .room(roomID: "1", via: []), expectedState: .roomList(selectedRoomID: "1")) try await process(route: .room(roomID: "1", via: []), expectedState: .roomList(roomListSelectedRoomID: "1"))
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator) XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
XCTAssertNotNil(detailCoordinator) XCTAssertNotNil(detailCoordinator)
try await process(route: .room(roomID: "2", via: []), expectedState: .roomList(selectedRoomID: "2")) try await process(route: .room(roomID: "2", via: []), expectedState: .roomList(roomListSelectedRoomID: "2"))
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator) XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
XCTAssertNotNil(detailCoordinator) XCTAssertNotNil(detailCoordinator)
try await process(route: .roomList, expectedState: .roomList(selectedRoomID: nil)) try await process(route: .roomList, expectedState: .roomList(roomListSelectedRoomID: nil))
XCTAssertNil(detailNavigationStack?.rootCoordinator) XCTAssertNil(detailNavigationStack?.rootCoordinator)
XCTAssertNil(detailCoordinator) XCTAssertNil(detailCoordinator)
@@ -78,25 +78,25 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
func testRoomAliasPresentation() async throws { func testRoomAliasPresentation() async throws {
clientProxy.resolveRoomAliasReturnValue = .success(.init(roomId: "1", servers: [])) clientProxy.resolveRoomAliasReturnValue = .success(.init(roomId: "1", servers: []))
try await process(route: .roomAlias("#alias:matrix.org"), expectedState: .roomList(selectedRoomID: "1")) try await process(route: .roomAlias("#alias:matrix.org"), expectedState: .roomList(roomListSelectedRoomID: "1"))
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator) XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
XCTAssertNotNil(detailCoordinator) XCTAssertNotNil(detailCoordinator)
try await process(route: .roomList, expectedState: .roomList(selectedRoomID: nil)) try await process(route: .roomList, expectedState: .roomList(roomListSelectedRoomID: nil))
XCTAssertNil(detailNavigationStack?.rootCoordinator) XCTAssertNil(detailNavigationStack?.rootCoordinator)
XCTAssertNil(detailCoordinator) XCTAssertNil(detailCoordinator)
try await process(route: .room(roomID: "1", via: []), expectedState: .roomList(selectedRoomID: "1")) try await process(route: .room(roomID: "1", via: []), expectedState: .roomList(roomListSelectedRoomID: "1"))
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator) XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
XCTAssertNotNil(detailCoordinator) XCTAssertNotNil(detailCoordinator)
clientProxy.resolveRoomAliasReturnValue = .success(.init(roomId: "2", servers: [])) clientProxy.resolveRoomAliasReturnValue = .success(.init(roomId: "2", servers: []))
try await process(route: .room(roomID: "2", via: []), expectedState: .roomList(selectedRoomID: "2")) try await process(route: .room(roomID: "2", via: []), expectedState: .roomList(roomListSelectedRoomID: "2"))
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator) XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
XCTAssertNotNil(detailCoordinator) XCTAssertNotNil(detailCoordinator)
try await process(route: .roomList, expectedState: .roomList(selectedRoomID: nil)) try await process(route: .roomList, expectedState: .roomList(roomListSelectedRoomID: nil))
XCTAssertNil(detailNavigationStack?.rootCoordinator) XCTAssertNil(detailNavigationStack?.rootCoordinator)
XCTAssertNil(detailCoordinator) XCTAssertNil(detailCoordinator)
@@ -104,27 +104,27 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
} }
func testRoomDetailsPresentation() async throws { func testRoomDetailsPresentation() async throws {
try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(selectedRoomID: "1")) try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(roomListSelectedRoomID: "1"))
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator) XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator)
XCTAssertNotNil(detailCoordinator) XCTAssertNotNil(detailCoordinator)
try await process(route: .roomList, expectedState: .roomList(selectedRoomID: nil)) try await process(route: .roomList, expectedState: .roomList(roomListSelectedRoomID: nil))
XCTAssertNil(detailNavigationStack?.rootCoordinator) XCTAssertNil(detailNavigationStack?.rootCoordinator)
XCTAssertNil(detailCoordinator) XCTAssertNil(detailCoordinator)
} }
func testStackUnwinding() async throws { func testStackUnwinding() async throws {
try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(selectedRoomID: "1")) try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(roomListSelectedRoomID: "1"))
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator) XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator)
XCTAssertNotNil(detailCoordinator) XCTAssertNotNil(detailCoordinator)
try await process(route: .room(roomID: "2", via: []), expectedState: .roomList(selectedRoomID: "2")) try await process(route: .room(roomID: "2", via: []), expectedState: .roomList(roomListSelectedRoomID: "2"))
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator) XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
XCTAssertNotNil(detailCoordinator) XCTAssertNotNil(detailCoordinator)
} }
func testNoOp() async throws { func testNoOp() async throws {
try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(selectedRoomID: "1")) try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(roomListSelectedRoomID: "1"))
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator) XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator)
XCTAssertNotNil(detailCoordinator) XCTAssertNotNil(detailCoordinator)
@@ -137,17 +137,17 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
} }
func testSwitchToDifferentDetails() async throws { func testSwitchToDifferentDetails() async throws {
try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(selectedRoomID: "1")) try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(roomListSelectedRoomID: "1"))
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator) XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator)
XCTAssertNotNil(detailCoordinator) XCTAssertNotNil(detailCoordinator)
try await process(route: .roomDetails(roomID: "2"), expectedState: .roomList(selectedRoomID: "2")) try await process(route: .roomDetails(roomID: "2"), expectedState: .roomList(roomListSelectedRoomID: "2"))
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator) XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator)
XCTAssertNotNil(detailCoordinator) XCTAssertNotNil(detailCoordinator)
} }
func testPushDetails() async throws { func testPushDetails() async throws {
try await process(route: .room(roomID: "1", via: []), expectedState: .roomList(selectedRoomID: "1")) try await process(route: .room(roomID: "1", via: []), expectedState: .roomList(roomListSelectedRoomID: "1"))
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator) XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
XCTAssertNotNil(detailCoordinator) XCTAssertNotNil(detailCoordinator)
@@ -162,17 +162,17 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
} }
func testReplaceDetailsWithTimeline() async throws { func testReplaceDetailsWithTimeline() async throws {
try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(selectedRoomID: "1")) try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(roomListSelectedRoomID: "1"))
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator) XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator)
XCTAssertNotNil(detailCoordinator) XCTAssertNotNil(detailCoordinator)
try await process(route: .room(roomID: "1", via: []), expectedState: .roomList(selectedRoomID: "1")) try await process(route: .room(roomID: "1", via: []), expectedState: .roomList(roomListSelectedRoomID: "1"))
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator) XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
XCTAssertNotNil(detailCoordinator) XCTAssertNotNil(detailCoordinator)
} }
func testUserProfileClearsStack() async throws { func testUserProfileClearsStack() async throws {
try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(selectedRoomID: "1")) try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(roomListSelectedRoomID: "1"))
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator) XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator)
XCTAssertNotNil(detailCoordinator) XCTAssertNotNil(detailCoordinator)
XCTAssertNil(splitCoordinator?.sheetCoordinator) XCTAssertNil(splitCoordinator?.sheetCoordinator)
@@ -187,7 +187,7 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
} }
func testRoomClearsStack() async throws { func testRoomClearsStack() async throws {
try await process(route: .room(roomID: "1", via: []), expectedState: .roomList(selectedRoomID: "1")) try await process(route: .room(roomID: "1", via: []), expectedState: .roomList(roomListSelectedRoomID: "1"))
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator) XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
XCTAssertEqual(detailNavigationStack?.stackCoordinators.count, 0) XCTAssertEqual(detailNavigationStack?.stackCoordinators.count, 0)
XCTAssertNotNil(detailCoordinator) XCTAssertNotNil(detailCoordinator)
@@ -199,7 +199,7 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
XCTAssertTrue(detailNavigationStack?.stackCoordinators.first is RoomScreenCoordinator) XCTAssertTrue(detailNavigationStack?.stackCoordinators.first is RoomScreenCoordinator)
XCTAssertNotNil(detailCoordinator) XCTAssertNotNil(detailCoordinator)
try await process(route: .room(roomID: "3", via: []), expectedState: .roomList(selectedRoomID: "3")) try await process(route: .room(roomID: "3", via: []), expectedState: .roomList(roomListSelectedRoomID: "3"))
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator) XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
XCTAssertEqual(detailNavigationStack?.stackCoordinators.count, 0) XCTAssertEqual(detailNavigationStack?.stackCoordinators.count, 0)
XCTAssertNotNil(detailCoordinator) XCTAssertNotNil(detailCoordinator)
@@ -207,7 +207,7 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
func testEventRoutes() async throws { func testEventRoutes() async throws {
// A regular event route should set its room as the root of the stack and focus on the event. // A regular event route should set its room as the root of the stack and focus on the event.
try await process(route: .event(eventID: "1", roomID: "1", via: []), expectedState: .roomList(selectedRoomID: "1")) try await process(route: .event(eventID: "1", roomID: "1", via: []), expectedState: .roomList(roomListSelectedRoomID: "1"))
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator) XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
XCTAssertEqual(detailNavigationStack?.stackCoordinators.count, 0) XCTAssertEqual(detailNavigationStack?.stackCoordinators.count, 0)
XCTAssertNotNil(detailCoordinator) XCTAssertNotNil(detailCoordinator)
@@ -225,7 +225,7 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
XCTAssertEqual(timelineControllerFactory.buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderReceivedArguments?.initialFocussedEventID, "2") XCTAssertEqual(timelineControllerFactory.buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderReceivedArguments?.initialFocussedEventID, "2")
// A subsequent regular event route should clear the stack and set the new room as the root of the stack. // A subsequent regular event route should clear the stack and set the new room as the root of the stack.
try await process(route: .event(eventID: "3", roomID: "3", via: []), expectedState: .roomList(selectedRoomID: "3")) try await process(route: .event(eventID: "3", roomID: "3", via: []), expectedState: .roomList(roomListSelectedRoomID: "3"))
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator) XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
XCTAssertEqual(detailNavigationStack?.stackCoordinators.count, 0) XCTAssertEqual(detailNavigationStack?.stackCoordinators.count, 0)
XCTAssertNotNil(detailCoordinator) XCTAssertNotNil(detailCoordinator)
@@ -233,7 +233,7 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
XCTAssertEqual(timelineControllerFactory.buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderReceivedArguments?.initialFocussedEventID, "3") XCTAssertEqual(timelineControllerFactory.buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderReceivedArguments?.initialFocussedEventID, "3")
// A regular event route for the same room should set a new instance of the room as the root of the stack. // A regular event route for the same room should set a new instance of the room as the root of the stack.
try await process(route: .event(eventID: "4", roomID: "3", via: []), expectedState: .roomList(selectedRoomID: "3")) try await process(route: .event(eventID: "4", roomID: "3", via: []), expectedState: .roomList(roomListSelectedRoomID: "3"))
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator) XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
XCTAssertEqual(detailNavigationStack?.stackCoordinators.count, 0) XCTAssertEqual(detailNavigationStack?.stackCoordinators.count, 0)
XCTAssertNotNil(detailCoordinator) XCTAssertNotNil(detailCoordinator)
@@ -243,7 +243,7 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
} }
func testShareMediaRouteWithoutRoom() async throws { func testShareMediaRouteWithoutRoom() async throws {
try await process(route: .settings, expectedState: .settingsScreen(selectedRoomID: nil)) try await process(route: .settings, expectedState: .settingsScreen(roomListSelectedRoomID: nil))
XCTAssertTrue((splitCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is SettingsScreenCoordinator) XCTAssertTrue((splitCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is SettingsScreenCoordinator)
let sharePayload: ShareExtensionPayload = .mediaFile(roomID: nil, mediaFile: .init(url: .picturesDirectory, suggestedName: nil)) let sharePayload: ShareExtensionPayload = .mediaFile(roomID: nil, mediaFile: .init(url: .picturesDirectory, suggestedName: nil))
@@ -254,19 +254,19 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
} }
func testShareMediaRouteWithRoom() async throws { func testShareMediaRouteWithRoom() async throws {
try await process(route: .event(eventID: "1", roomID: "1", via: []), expectedState: .roomList(selectedRoomID: "1")) try await process(route: .event(eventID: "1", roomID: "1", via: []), expectedState: .roomList(roomListSelectedRoomID: "1"))
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator) XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
let sharePayload: ShareExtensionPayload = .mediaFile(roomID: "2", mediaFile: .init(url: .picturesDirectory, suggestedName: nil)) let sharePayload: ShareExtensionPayload = .mediaFile(roomID: "2", mediaFile: .init(url: .picturesDirectory, suggestedName: nil))
try await process(route: .share(sharePayload), try await process(route: .share(sharePayload),
expectedState: .roomList(selectedRoomID: "2")) expectedState: .roomList(roomListSelectedRoomID: "2"))
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator) XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
XCTAssertTrue((splitCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is MediaUploadPreviewScreenCoordinator) XCTAssertTrue((splitCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is MediaUploadPreviewScreenCoordinator)
} }
func testShareTextRouteWithoutRoom() async throws { func testShareTextRouteWithoutRoom() async throws {
try await process(route: .settings, expectedState: .settingsScreen(selectedRoomID: nil)) try await process(route: .settings, expectedState: .settingsScreen(roomListSelectedRoomID: nil))
XCTAssertTrue((splitCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is SettingsScreenCoordinator) XCTAssertTrue((splitCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is SettingsScreenCoordinator)
let sharePayload: ShareExtensionPayload = .text(roomID: nil, text: "Important Text") let sharePayload: ShareExtensionPayload = .text(roomID: nil, text: "Important Text")
@@ -277,12 +277,12 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
} }
func testShareTextRouteWithRoom() async throws { func testShareTextRouteWithRoom() async throws {
try await process(route: .event(eventID: "1", roomID: "1", via: []), expectedState: .roomList(selectedRoomID: "1")) try await process(route: .event(eventID: "1", roomID: "1", via: []), expectedState: .roomList(roomListSelectedRoomID: "1"))
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator) XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
let sharePayload: ShareExtensionPayload = .text(roomID: "2", text: "Important text") let sharePayload: ShareExtensionPayload = .text(roomID: "2", text: "Important text")
try await process(route: .share(sharePayload), try await process(route: .share(sharePayload),
expectedState: .roomList(selectedRoomID: "2")) expectedState: .roomList(roomListSelectedRoomID: "2"))
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator) XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
XCTAssertNil(splitCoordinator?.sheetCoordinator, "The media upload sheet shouldn't be shown when sharing text.") XCTAssertNil(splitCoordinator?.sheetCoordinator, "The media upload sheet shouldn't be shown when sharing text.")