Fixes #2486 - Implement blocked users management in the app settings

This commit is contained in:
Stefan Ceriu
2024-02-28 13:21:54 +02:00
committed by GitHub
parent a0b510cb6b
commit 7615e97c7f
46 changed files with 624 additions and 98 deletions
+40
View File
@@ -217,6 +217,7 @@
35E975CFDA60E05362A7CF79 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 1222DB76B917EB8A55365BA5 /* target.yml */; }; 35E975CFDA60E05362A7CF79 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 1222DB76B917EB8A55365BA5 /* target.yml */; };
366D5BFE52CB79E804C7D095 /* CallScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAD9547E47C58930E2CE8306 /* CallScreenViewModelTests.swift */; }; 366D5BFE52CB79E804C7D095 /* CallScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAD9547E47C58930E2CE8306 /* CallScreenViewModelTests.swift */; };
368C8758FCD079E6AAA18C2C /* NoticeRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */; }; 368C8758FCD079E6AAA18C2C /* NoticeRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */; };
369BF960E52BBEE61F8A5BD1 /* BlockedUsersScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED60E4D2CD678E1EBF16F77A /* BlockedUsersScreen.swift */; };
36AC963F2F04069B7FF1AA0C /* UIConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6D88E8AFFBF2C1D589C0FA /* UIConstants.swift */; }; 36AC963F2F04069B7FF1AA0C /* UIConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6D88E8AFFBF2C1D589C0FA /* UIConstants.swift */; };
36AD4DD4C798E22584ED3200 /* Emojibase in Frameworks */ = {isa = PBXBuildFile; productRef = C05729B1684C331F5FFE9232 /* Emojibase */; }; 36AD4DD4C798E22584ED3200 /* Emojibase in Frameworks */ = {isa = PBXBuildFile; productRef = C05729B1684C331F5FFE9232 /* Emojibase */; };
36CD6E11B37396E14F032CB6 /* WysiwygComposer in Frameworks */ = {isa = PBXBuildFile; productRef = CA07D57389DACE18AEB6A5E2 /* WysiwygComposer */; }; 36CD6E11B37396E14F032CB6 /* WysiwygComposer in Frameworks */ = {isa = PBXBuildFile; productRef = CA07D57389DACE18AEB6A5E2 /* WysiwygComposer */; };
@@ -562,6 +563,7 @@
8C1A5ECAF895D4CAF8C4D461 /* AppActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F21ED7205048668BEB44A38 /* AppActivityView.swift */; }; 8C1A5ECAF895D4CAF8C4D461 /* AppActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F21ED7205048668BEB44A38 /* AppActivityView.swift */; };
8C42B5B1642D189C362A5EDF /* SecureBackupScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91831D7042EADD0CC2B5EC36 /* SecureBackupScreenUITests.swift */; }; 8C42B5B1642D189C362A5EDF /* SecureBackupScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91831D7042EADD0CC2B5EC36 /* SecureBackupScreenUITests.swift */; };
8C706DA7EAC0974CA2F8F1CD /* MentionBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15748C254911E3654C93B0ED /* MentionBuilder.swift */; }; 8C706DA7EAC0974CA2F8F1CD /* MentionBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15748C254911E3654C93B0ED /* MentionBuilder.swift */; };
8C91D242BEEC657FABCC0B95 /* BlockedUsersScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8642512079EEFD622E3AA66B /* BlockedUsersScreenModels.swift */; };
8CC12086CBF91A7E10CDC205 /* HomeScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D653265D006E708E4E51AD64 /* HomeScreenCoordinator.swift */; }; 8CC12086CBF91A7E10CDC205 /* HomeScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D653265D006E708E4E51AD64 /* HomeScreenCoordinator.swift */; };
8D3E1FADD78E72504DE0E402 /* UserAgentBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB3B237387B8288A5A938F1B /* UserAgentBuilderTests.swift */; }; 8D3E1FADD78E72504DE0E402 /* UserAgentBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB3B237387B8288A5A938F1B /* UserAgentBuilderTests.swift */; };
8D71E5E53F372202379BECCE /* BugReportScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 303FCADE77DF1F3670C086ED /* BugReportScreenViewModel.swift */; }; 8D71E5E53F372202379BECCE /* BugReportScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 303FCADE77DF1F3670C086ED /* BugReportScreenViewModel.swift */; };
@@ -582,6 +584,7 @@
92720AB0DA9AB5EEF1DAF56B /* SecureBackupLogoutConfirmationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC017C3CB6B0F7C63F460F2 /* SecureBackupLogoutConfirmationScreenViewModel.swift */; }; 92720AB0DA9AB5EEF1DAF56B /* SecureBackupLogoutConfirmationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC017C3CB6B0F7C63F460F2 /* SecureBackupLogoutConfirmationScreenViewModel.swift */; };
9278EC51D24E57445B290521 /* AudioSessionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB284643AF7AB131E307DCE0 /* AudioSessionProtocol.swift */; }; 9278EC51D24E57445B290521 /* AudioSessionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB284643AF7AB131E307DCE0 /* AudioSessionProtocol.swift */; };
92D9088B901CEBB1A99ECA4E /* RoomMemberProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36FD673E24FBFCFDF398716A /* RoomMemberProxyMock.swift */; }; 92D9088B901CEBB1A99ECA4E /* RoomMemberProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36FD673E24FBFCFDF398716A /* RoomMemberProxyMock.swift */; };
934051B17A884AB0635DF81B /* BlockedUsersScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A010B8EAD1A9F6B4686DF2F4 /* BlockedUsersScreenViewModel.swift */; };
93875ADD456142D20823ED24 /* ServerSelectionViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */; }; 93875ADD456142D20823ED24 /* ServerSelectionViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */; };
93A549135E6C027A0D823BFE /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 593FBBF394712F2963E98A0B /* DTCoreText */; }; 93A549135E6C027A0D823BFE /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 593FBBF394712F2963E98A0B /* DTCoreText */; };
93BA4A81B6D893271101F9F0 /* DeviceKit in Frameworks */ = {isa = PBXBuildFile; productRef = A7CA6F33C553805035C3B114 /* DeviceKit */; }; 93BA4A81B6D893271101F9F0 /* DeviceKit in Frameworks */ = {isa = PBXBuildFile; productRef = A7CA6F33C553805035C3B114 /* DeviceKit */; };
@@ -593,6 +596,7 @@
94D0F36A87E596A93C0C178A /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E89E530A8E92EC44301CA1 /* Bundle.swift */; }; 94D0F36A87E596A93C0C178A /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E89E530A8E92EC44301CA1 /* Bundle.swift */; };
95690DDD9D547D3D842ACBE3 /* AnalyticsSettingsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD371B60E07A5324B9507EF /* AnalyticsSettingsScreenCoordinator.swift */; }; 95690DDD9D547D3D842ACBE3 /* AnalyticsSettingsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD371B60E07A5324B9507EF /* AnalyticsSettingsScreenCoordinator.swift */; };
9586E90A447C4896C0CA3A8E /* TimelineItemReplyDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE89A8BD65CCE3FCC925CA14 /* TimelineItemReplyDetails.swift */; }; 9586E90A447C4896C0CA3A8E /* TimelineItemReplyDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE89A8BD65CCE3FCC925CA14 /* TimelineItemReplyDetails.swift */; };
95E7B236F7116CACE05A6BC9 /* BlockedUsersScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A16D0F226B1819D017531647 /* BlockedUsersScreenCoordinator.swift */; };
962A4F8AD6312804E2C6BB6E /* PhotoLibraryPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A232D9156D225BD9FD1D0C43 /* PhotoLibraryPicker.swift */; }; 962A4F8AD6312804E2C6BB6E /* PhotoLibraryPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A232D9156D225BD9FD1D0C43 /* PhotoLibraryPicker.swift */; };
964B9D2EC38C488C360CE0C9 /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = B902EA6CD3296B0E10EE432B /* HomeScreen.swift */; }; 964B9D2EC38C488C360CE0C9 /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = B902EA6CD3296B0E10EE432B /* HomeScreen.swift */; };
968A5B890004526AB58A217C /* AvatarSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24B88AD3D1599E8CB1376E0 /* AvatarSize.swift */; }; 968A5B890004526AB58A217C /* AvatarSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24B88AD3D1599E8CB1376E0 /* AvatarSize.swift */; };
@@ -657,6 +661,7 @@
A440D4BC02088482EC633A88 /* KeychainControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E94DCFEE803E5ABAE8ACCE /* KeychainControllerProtocol.swift */; }; A440D4BC02088482EC633A88 /* KeychainControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E94DCFEE803E5ABAE8ACCE /* KeychainControllerProtocol.swift */; };
A494741843F087881299ACF0 /* RestorationToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3558A15CFB934F9229301527 /* RestorationToken.swift */; }; A494741843F087881299ACF0 /* RestorationToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3558A15CFB934F9229301527 /* RestorationToken.swift */; };
A4B0BAD62A12ED76BD611B79 /* BadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FA515B3B0D61EF1E907D2D /* BadgeView.swift */; }; A4B0BAD62A12ED76BD611B79 /* BadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FA515B3B0D61EF1E907D2D /* BadgeView.swift */; };
A4B123C635F70DDD4BC2FAC9 /* BlockedUsersScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E76A706B3EEA32B882DA5E2D /* BlockedUsersScreenViewModelProtocol.swift */; };
A4C29D373986AFE4559696D5 /* SecureBackupKeyBackupScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4525E8C0FBDD27D1ACE90952 /* SecureBackupKeyBackupScreenViewModelProtocol.swift */; }; A4C29D373986AFE4559696D5 /* SecureBackupKeyBackupScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4525E8C0FBDD27D1ACE90952 /* SecureBackupKeyBackupScreenViewModelProtocol.swift */; };
A4E885358D7DD5A072A06824 /* PostHog in Frameworks */ = {isa = PBXBuildFile; productRef = CCE5BF78B125320CBF3BB834 /* PostHog */; }; A4E885358D7DD5A072A06824 /* PostHog in Frameworks */ = {isa = PBXBuildFile; productRef = CCE5BF78B125320CBF3BB834 /* PostHog */; };
A5B9EF45C7B8ACEB4954AE36 /* LoginScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9780389F8A53E4D26E23DD03 /* LoginScreenViewModelProtocol.swift */; }; A5B9EF45C7B8ACEB4954AE36 /* LoginScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9780389F8A53E4D26E23DD03 /* LoginScreenViewModelProtocol.swift */; };
@@ -831,6 +836,7 @@
CE1694C7BB93C3311524EF28 /* Untranslated.strings in Resources */ = {isa = PBXBuildFile; fileRef = D2F7194F440375338F8E2487 /* Untranslated.strings */; }; CE1694C7BB93C3311524EF28 /* Untranslated.strings in Resources */ = {isa = PBXBuildFile; fileRef = D2F7194F440375338F8E2487 /* Untranslated.strings */; };
CE6F237360875D3D573FD0B2 /* RoomNotificationSettingsProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6B522BD637845AB9570B10 /* RoomNotificationSettingsProxy.swift */; }; CE6F237360875D3D573FD0B2 /* RoomNotificationSettingsProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6B522BD637845AB9570B10 /* RoomNotificationSettingsProxy.swift */; };
CE9530A4CA661E090635C2F2 /* NotificationItemProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25F7FE40EF7490A7E09D7BE6 /* NotificationItemProxy.swift */; }; CE9530A4CA661E090635C2F2 /* NotificationItemProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25F7FE40EF7490A7E09D7BE6 /* NotificationItemProxy.swift */; };
CEAEA57B7665C8E790599A78 /* BlockedUsersScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 240610DF32F3213BEC5611D7 /* BlockedUsersScreenViewModelTests.swift */; };
CEB8FB1269DE20536608B957 /* LoginMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B41FABA2B0AEF4389986495 /* LoginMode.swift */; }; CEB8FB1269DE20536608B957 /* LoginMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B41FABA2B0AEF4389986495 /* LoginMode.swift */; };
CF3827071B0BC9638BD44F5D /* WaitlistScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB58EF0176D4CFB1040DA22 /* WaitlistScreenViewModel.swift */; }; CF3827071B0BC9638BD44F5D /* WaitlistScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB58EF0176D4CFB1040DA22 /* WaitlistScreenViewModel.swift */; };
CF38B70D8C6DD42C00A56A27 /* LogViewerScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A84D413BF49F0E980F010A6B /* LogViewerScreenCoordinator.swift */; }; CF38B70D8C6DD42C00A56A27 /* LogViewerScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A84D413BF49F0E980F010A6B /* LogViewerScreenCoordinator.swift */; };
@@ -1223,6 +1229,7 @@
2355398E4A55DA5A89128AD1 /* EncryptionKeyProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionKeyProvider.swift; sourceTree = "<group>"; }; 2355398E4A55DA5A89128AD1 /* EncryptionKeyProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionKeyProvider.swift; sourceTree = "<group>"; };
2389732B0E115A999A069083 /* NotificationSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsScreenCoordinator.swift; sourceTree = "<group>"; }; 2389732B0E115A999A069083 /* NotificationSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsScreenCoordinator.swift; sourceTree = "<group>"; };
23AA3F4B285570805CB0CCDD /* MapTiler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTiler.swift; sourceTree = "<group>"; }; 23AA3F4B285570805CB0CCDD /* MapTiler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTiler.swift; sourceTree = "<group>"; };
240610DF32F3213BEC5611D7 /* BlockedUsersScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUsersScreenViewModelTests.swift; sourceTree = "<group>"; };
24227FF9A2797F6EA7F69CDD /* HomeScreenInvitesButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenInvitesButton.swift; sourceTree = "<group>"; }; 24227FF9A2797F6EA7F69CDD /* HomeScreenInvitesButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenInvitesButton.swift; sourceTree = "<group>"; };
2429224EB0EEA34D35CE9249 /* UserIndicatorControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorControllerTests.swift; sourceTree = "<group>"; }; 2429224EB0EEA34D35CE9249 /* UserIndicatorControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorControllerTests.swift; sourceTree = "<group>"; };
2441E2424E78A40FC95DBA76 /* AudioRecorderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecorderTests.swift; sourceTree = "<group>"; }; 2441E2424E78A40FC95DBA76 /* AudioRecorderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecorderTests.swift; sourceTree = "<group>"; };
@@ -1582,6 +1589,7 @@
85EB16E7FE59A947CA441531 /* MediaProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProviderProtocol.swift; sourceTree = "<group>"; }; 85EB16E7FE59A947CA441531 /* MediaProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProviderProtocol.swift; sourceTree = "<group>"; };
8610C1D21565C950BCA6A454 /* AppLockSetupSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupSettingsScreenViewModelProtocol.swift; sourceTree = "<group>"; }; 8610C1D21565C950BCA6A454 /* AppLockSetupSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupSettingsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
86376BEE425704AEE197CA54 /* PillContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillContext.swift; sourceTree = "<group>"; }; 86376BEE425704AEE197CA54 /* PillContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillContext.swift; sourceTree = "<group>"; };
8642512079EEFD622E3AA66B /* BlockedUsersScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUsersScreenModels.swift; sourceTree = "<group>"; };
86873A768B13069BB5CAECF6 /* InvitesScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenViewModelProtocol.swift; sourceTree = "<group>"; }; 86873A768B13069BB5CAECF6 /* InvitesScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenViewModelProtocol.swift; sourceTree = "<group>"; };
86A6F283BC574FDB96ABBB07 /* DeveloperOptionsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenViewModel.swift; sourceTree = "<group>"; }; 86A6F283BC574FDB96ABBB07 /* DeveloperOptionsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenViewModel.swift; sourceTree = "<group>"; };
86C8CE2630F54D5FE1591786 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/InfoPlist.strings; sourceTree = "<group>"; }; 86C8CE2630F54D5FE1591786 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/InfoPlist.strings; sourceTree = "<group>"; };
@@ -1665,6 +1673,7 @@
9F85164F9475FF2867F71AAA /* RoomTimelineController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineController.swift; sourceTree = "<group>"; }; 9F85164F9475FF2867F71AAA /* RoomTimelineController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineController.swift; sourceTree = "<group>"; };
9FB4F169D653296023ED65E6 /* NSESettingsProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSESettingsProtocol.swift; sourceTree = "<group>"; }; 9FB4F169D653296023ED65E6 /* NSESettingsProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSESettingsProtocol.swift; sourceTree = "<group>"; };
A00C7A331B72C0F05C00392F /* RoomScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModelProtocol.swift; sourceTree = "<group>"; }; A00C7A331B72C0F05C00392F /* RoomScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModelProtocol.swift; sourceTree = "<group>"; };
A010B8EAD1A9F6B4686DF2F4 /* BlockedUsersScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUsersScreenViewModel.swift; sourceTree = "<group>"; };
A019A12C866D64CF072024B9 /* AppLockSetupPINScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupPINScreenViewModel.swift; sourceTree = "<group>"; }; A019A12C866D64CF072024B9 /* AppLockSetupPINScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupPINScreenViewModel.swift; sourceTree = "<group>"; };
A02D1A490944BF01A37586E1 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/SAS.strings; sourceTree = "<group>"; }; A02D1A490944BF01A37586E1 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/SAS.strings; sourceTree = "<group>"; };
A05707BF550D770168A406DB /* LoginViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModelTests.swift; sourceTree = "<group>"; }; A05707BF550D770168A406DB /* LoginViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModelTests.swift; sourceTree = "<group>"; };
@@ -1673,6 +1682,7 @@
A12D3B1BCF920880CA8BBB6B /* UserIndicatorControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorControllerProtocol.swift; sourceTree = "<group>"; }; A12D3B1BCF920880CA8BBB6B /* UserIndicatorControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorControllerProtocol.swift; sourceTree = "<group>"; };
A130A2251A15A7AACC84FD37 /* RoomPollsHistoryScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPollsHistoryScreenViewModelProtocol.swift; sourceTree = "<group>"; }; A130A2251A15A7AACC84FD37 /* RoomPollsHistoryScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPollsHistoryScreenViewModelProtocol.swift; sourceTree = "<group>"; };
A16CD2C62CB7DB78A4238485 /* ReportContentScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportContentScreenCoordinator.swift; sourceTree = "<group>"; }; A16CD2C62CB7DB78A4238485 /* ReportContentScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportContentScreenCoordinator.swift; sourceTree = "<group>"; };
A16D0F226B1819D017531647 /* BlockedUsersScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUsersScreenCoordinator.swift; sourceTree = "<group>"; };
A1BF12A5E7C76777C4BF0F2B /* TimelineItemMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemMenu.swift; sourceTree = "<group>"; }; A1BF12A5E7C76777C4BF0F2B /* TimelineItemMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemMenu.swift; sourceTree = "<group>"; };
A1C22B1B5FA3A765EADB2CC9 /* SessionVerificationStateMachineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationStateMachineTests.swift; sourceTree = "<group>"; }; A1C22B1B5FA3A765EADB2CC9 /* SessionVerificationStateMachineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationStateMachineTests.swift; sourceTree = "<group>"; };
A232D9156D225BD9FD1D0C43 /* PhotoLibraryPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoLibraryPicker.swift; sourceTree = "<group>"; }; A232D9156D225BD9FD1D0C43 /* PhotoLibraryPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoLibraryPicker.swift; sourceTree = "<group>"; };
@@ -1957,6 +1967,7 @@
E6F5D66F158A6662F953733E /* NotificationSettingsProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsProxy.swift; sourceTree = "<group>"; }; E6F5D66F158A6662F953733E /* NotificationSettingsProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsProxy.swift; sourceTree = "<group>"; };
E6FCC416A3BFE73DF7B3E6BF /* RoomTimelineControllerFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineControllerFactory.swift; sourceTree = "<group>"; }; E6FCC416A3BFE73DF7B3E6BF /* RoomTimelineControllerFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineControllerFactory.swift; sourceTree = "<group>"; };
E71C28CF29CD05B6D6AE8580 /* HomeScreenSessionVerificationBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenSessionVerificationBanner.swift; sourceTree = "<group>"; }; E71C28CF29CD05B6D6AE8580 /* HomeScreenSessionVerificationBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenSessionVerificationBanner.swift; sourceTree = "<group>"; };
E76A706B3EEA32B882DA5E2D /* BlockedUsersScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUsersScreenViewModelProtocol.swift; sourceTree = "<group>"; };
E78FC546F28E045A560F2963 /* EncryptionKeyProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionKeyProviderProtocol.swift; sourceTree = "<group>"; }; E78FC546F28E045A560F2963 /* EncryptionKeyProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionKeyProviderProtocol.swift; sourceTree = "<group>"; };
E8294DB9E95C0C0630418466 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; }; E8294DB9E95C0C0630418466 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
E8774CF614849664B5B3C2A1 /* UserSessionFlowCoordinatorStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionFlowCoordinatorStateMachine.swift; sourceTree = "<group>"; }; E8774CF614849664B5B3C2A1 /* UserSessionFlowCoordinatorStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionFlowCoordinatorStateMachine.swift; sourceTree = "<group>"; };
@@ -1980,6 +1991,7 @@
ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = "<group>"; }; ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenRoomCell.swift; sourceTree = "<group>"; };
ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = "<group>"; }; ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = "<group>"; };
ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; path = message.caf; sourceTree = "<group>"; }; ED482057AE39D5C6D9C5F3D8 /* message.caf */ = {isa = PBXFileReference; path = message.caf; sourceTree = "<group>"; };
ED60E4D2CD678E1EBF16F77A /* BlockedUsersScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUsersScreen.swift; sourceTree = "<group>"; };
ED983D4DCA5AFA6E1ED96099 /* StateRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRoomTimelineView.swift; sourceTree = "<group>"; }; ED983D4DCA5AFA6E1ED96099 /* StateRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateRoomTimelineView.swift; sourceTree = "<group>"; };
EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelTests.swift; sourceTree = "<group>"; }; EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelTests.swift; sourceTree = "<group>"; };
EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItemContent.swift; sourceTree = "<group>"; }; EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItemContent.swift; sourceTree = "<group>"; };
@@ -2510,6 +2522,14 @@
path = ServerSelectionScreen; path = ServerSelectionScreen;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
2E035B978E415C77423FA3C2 /* View */ = {
isa = PBXGroup;
children = (
ED60E4D2CD678E1EBF16F77A /* BlockedUsersScreen.swift */,
);
path = View;
sourceTree = "<group>";
};
2ECFF6B05DAA37EB10DBF7E8 /* UITests */ = { 2ECFF6B05DAA37EB10DBF7E8 /* UITests */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -3352,6 +3372,7 @@
C55CC239AE12339C565F6C9A /* AudioRecorderStateTests.swift */, C55CC239AE12339C565F6C9A /* AudioRecorderStateTests.swift */,
2441E2424E78A40FC95DBA76 /* AudioRecorderTests.swift */, 2441E2424E78A40FC95DBA76 /* AudioRecorderTests.swift */,
6DFCAA239095A116976E32C4 /* BackgroundTaskTests.swift */, 6DFCAA239095A116976E32C4 /* BackgroundTaskTests.swift */,
240610DF32F3213BEC5611D7 /* BlockedUsersScreenViewModelTests.swift */,
EFFD3200F9960D4996159F10 /* BugReportServiceTests.swift */, EFFD3200F9960D4996159F10 /* BugReportServiceTests.swift */,
7AB7ED3A898B07976F3AA90F /* BugReportViewModelTests.swift */, 7AB7ED3A898B07976F3AA90F /* BugReportViewModelTests.swift */,
CAD9547E47C58930E2CE8306 /* CallScreenViewModelTests.swift */, CAD9547E47C58930E2CE8306 /* CallScreenViewModelTests.swift */,
@@ -4623,6 +4644,7 @@
669239C03835CD8B51E0FFDB /* AnalyticsPromptScreen */, 669239C03835CD8B51E0FFDB /* AnalyticsPromptScreen */,
13263FFEA7749D822B51AA90 /* AppLock */, 13263FFEA7749D822B51AA90 /* AppLock */,
E74CD7681375AD2EAA34D66B /* Authentication */, E74CD7681375AD2EAA34D66B /* Authentication */,
EFD4F7FCAAAB3EF45EE7A067 /* BlockedUsersScreen */,
53FB148CD26AFB6A5B9E20B3 /* BugReportScreen */, 53FB148CD26AFB6A5B9E20B3 /* BugReportScreen */,
1185EECDD07495D65AC84AFC /* CallScreen */, 1185EECDD07495D65AC84AFC /* CallScreen */,
27F2500AC8736AAE774520C0 /* ComposerToolbar */, 27F2500AC8736AAE774520C0 /* ComposerToolbar */,
@@ -4771,6 +4793,18 @@
path = StartChatScreen; path = StartChatScreen;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
EFD4F7FCAAAB3EF45EE7A067 /* BlockedUsersScreen */ = {
isa = PBXGroup;
children = (
A16D0F226B1819D017531647 /* BlockedUsersScreenCoordinator.swift */,
8642512079EEFD622E3AA66B /* BlockedUsersScreenModels.swift */,
A010B8EAD1A9F6B4686DF2F4 /* BlockedUsersScreenViewModel.swift */,
E76A706B3EEA32B882DA5E2D /* BlockedUsersScreenViewModelProtocol.swift */,
2E035B978E415C77423FA3C2 /* View */,
);
path = BlockedUsersScreen;
sourceTree = "<group>";
};
F12966DF3DA87FEF21348D60 /* InviteUsersScreen */ = { F12966DF3DA87FEF21348D60 /* InviteUsersScreen */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -5393,6 +5427,7 @@
3042527CB344A9EF1157FC26 /* AudioRecorderStateTests.swift in Sources */, 3042527CB344A9EF1157FC26 /* AudioRecorderStateTests.swift in Sources */,
192A3CDCD0174AD1E4A128E4 /* AudioRecorderTests.swift in Sources */, 192A3CDCD0174AD1E4A128E4 /* AudioRecorderTests.swift in Sources */,
0F9E38A75337D0146652ACAB /* BackgroundTaskTests.swift in Sources */, 0F9E38A75337D0146652ACAB /* BackgroundTaskTests.swift in Sources */,
CEAEA57B7665C8E790599A78 /* BlockedUsersScreenViewModelTests.swift in Sources */,
7F61F9ACD5EC9E845EF3EFBF /* BugReportServiceTests.swift in Sources */, 7F61F9ACD5EC9E845EF3EFBF /* BugReportServiceTests.swift in Sources */,
C7CFDB4929DDD9A3B5BA085D /* BugReportViewModelTests.swift in Sources */, C7CFDB4929DDD9A3B5BA085D /* BugReportViewModelTests.swift in Sources */,
366D5BFE52CB79E804C7D095 /* CallScreenViewModelTests.swift in Sources */, 366D5BFE52CB79E804C7D095 /* CallScreenViewModelTests.swift in Sources */,
@@ -5590,6 +5625,11 @@
A4B0BAD62A12ED76BD611B79 /* BadgeView.swift in Sources */, A4B0BAD62A12ED76BD611B79 /* BadgeView.swift in Sources */,
38546A6010A2CF240EC9AF73 /* BindableState.swift in Sources */, 38546A6010A2CF240EC9AF73 /* BindableState.swift in Sources */,
EB9F4688006B52E69DF5358F /* BlankFormCoordinator.swift in Sources */, EB9F4688006B52E69DF5358F /* BlankFormCoordinator.swift in Sources */,
369BF960E52BBEE61F8A5BD1 /* BlockedUsersScreen.swift in Sources */,
95E7B236F7116CACE05A6BC9 /* BlockedUsersScreenCoordinator.swift in Sources */,
8C91D242BEEC657FABCC0B95 /* BlockedUsersScreenModels.swift in Sources */,
934051B17A884AB0635DF81B /* BlockedUsersScreenViewModel.swift in Sources */,
A4B123C635F70DDD4BC2FAC9 /* BlockedUsersScreenViewModelProtocol.swift in Sources */,
5EE1D4E316D66943E97FDCF2 /* BloomView.swift in Sources */, 5EE1D4E316D66943E97FDCF2 /* BloomView.swift in Sources */,
B6DF6B6FA8734B70F9BF261E /* BlurHashDecode.swift in Sources */, B6DF6B6FA8734B70F9BF261E /* BlurHashDecode.swift in Sources */,
E794AB6ABE1FF5AF0573FEA1 /* BlurHashEncode.swift in Sources */, E794AB6ABE1FF5AF0573FEA1 /* BlurHashEncode.swift in Sources */,
@@ -336,6 +336,7 @@
"screen_app_lock_setup_pin_mismatch_dialog_title" = "PINs don't match"; "screen_app_lock_setup_pin_mismatch_dialog_title" = "PINs don't match";
"screen_app_lock_signout_alert_message" = "You’ll need to re-login and create a new PIN to proceed"; "screen_app_lock_signout_alert_message" = "You’ll need to re-login and create a new PIN to proceed";
"screen_app_lock_signout_alert_title" = "You are being signed out"; "screen_app_lock_signout_alert_title" = "You are being signed out";
"screen_blocked_users_empty" = "You have no blocked users";
"screen_blocked_users_unblocking" = "Unblocking..."; "screen_blocked_users_unblocking" = "Unblocking...";
"screen_bug_report_attach_screenshot" = "Attach screenshot"; "screen_bug_report_attach_screenshot" = "Attach screenshot";
"screen_bug_report_contact_me" = "You may contact me if you have any follow up questions."; "screen_bug_report_contact_me" = "You may contact me if you have any follow up questions.";
@@ -491,6 +492,16 @@
"screen_room_attachment_source_location" = "Location"; "screen_room_attachment_source_location" = "Location";
"screen_room_attachment_source_poll" = "Poll"; "screen_room_attachment_source_poll" = "Poll";
"screen_room_attachment_text_formatting" = "Text Formatting"; "screen_room_attachment_text_formatting" = "Text Formatting";
"screen_room_change_permissions_administrators" = "Admins only";
"screen_room_change_permissions_ban_people" = "Ban people";
"screen_room_change_permissions_delete_messages" = "Delete messages";
"screen_room_change_permissions_invite_people" = "Invite people";
"screen_room_change_permissions_moderators" = "Admins and moderators";
"screen_room_change_permissions_remove_people" = "Remove people";
"screen_room_change_permissions_room_avatar" = "Change Room Avatar";
"screen_room_change_permissions_room_name" = "Change Room Name";
"screen_room_change_permissions_room_topic" = "Change Room Topic";
"screen_room_change_permissions_send_messages" = "Send messages";
"screen_room_change_role_confirm_add_admin_description" = "You will not be able to undo this action. You are promoting the user to have the same power level as you."; "screen_room_change_role_confirm_add_admin_description" = "You will not be able to undo this action. You are promoting the user to have the same power level as you.";
"screen_room_change_role_confirm_add_admin_title" = "Add Admin?"; "screen_room_change_role_confirm_add_admin_title" = "Add Admin?";
"screen_room_change_role_confirm_demote_self_action" = "Demote"; "screen_room_change_role_confirm_demote_self_action" = "Demote";
@@ -736,6 +747,10 @@
"screen_login_subtitle" = "Matrix is an open network for secure, decentralised communication."; "screen_login_subtitle" = "Matrix is an open network for secure, decentralised communication.";
"screen_report_content_block_user" = "Block user"; "screen_report_content_block_user" = "Block user";
"screen_room_attachment_source_camera_photo" = "Take photo"; "screen_room_attachment_source_camera_photo" = "Take photo";
"screen_room_change_permissions_everyone" = "Everyone";
"screen_room_change_permissions_member_moderation" = "Member moderation";
"screen_room_change_permissions_messages_and_content" = "Messages and content";
"screen_room_change_permissions_room_details" = "Room details";
"screen_room_details_invite_people_title" = "Invite people"; "screen_room_details_invite_people_title" = "Invite people";
"screen_room_details_leave_conversation_title" = "Leave conversation"; "screen_room_details_leave_conversation_title" = "Leave conversation";
"screen_room_details_leave_room_title" = "Leave room"; "screen_room_details_leave_room_title" = "Leave room";
@@ -419,7 +419,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
Task { Task {
let credentials = SoftLogoutScreenCredentials(userID: userSession.userID, let credentials = SoftLogoutScreenCredentials(userID: userSession.userID,
homeserverName: userSession.homeserver, homeserverName: userSession.homeserver,
userDisplayName: userSession.clientProxy.userDisplayName.value ?? "", userDisplayName: userSession.clientProxy.userDisplayNamePublisher.value ?? "",
deviceID: userSession.deviceID) deviceID: userSession.deviceID)
let authenticationService = AuthenticationServiceProxy(userSessionStore: userSessionStore, let authenticationService = AuthenticationServiceProxy(userSessionStore: userSessionStore,
@@ -866,7 +866,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
// Be a good citizen, run for a max of 10 SS responses or 10 seconds // Be a good citizen, run for a max of 10 SS responses or 10 seconds
// An SS request will time out after 30 seconds if no new data is available // An SS request will time out after 30 seconds if no new data is available
backgroundRefreshSyncObserver = userSession.clientProxy backgroundRefreshSyncObserver = userSession.clientProxy
.callbacks .actionsPublisher
.filter(\.isSyncUpdate) .filter(\.isSyncUpdate)
.collect(.byTimeOrCount(DispatchQueue.main, .seconds(10), 10)) .collect(.byTimeOrCount(DispatchQueue.main, .seconds(10), 10))
.sink(receiveValue: { [weak self] _ in .sink(receiveValue: { [weak self] _ in
@@ -128,6 +128,8 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
bugReportFlowCoordinator?.start() bugReportFlowCoordinator?.start()
case .about: case .about:
presentLegalInformationScreen() presentLegalInformationScreen()
case .blockedUsers:
presentBlockedUsersScreen()
case .sessionVerification: case .sessionVerification:
presentSessionVerificationScreen() presentSessionVerificationScreen()
case .accountSessions: case .accountSessions:
@@ -203,6 +205,12 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
navigationStackCoordinator.push(LegalInformationScreenCoordinator(appSettings: parameters.appSettings)) navigationStackCoordinator.push(LegalInformationScreenCoordinator(appSettings: parameters.appSettings))
} }
private func presentBlockedUsersScreen() {
let coordinator = BlockedUsersScreenCoordinator(parameters: .init(clientProxy: parameters.userSession.clientProxy,
userIndicatorController: parameters.userIndicatorController))
navigationStackCoordinator.push(coordinator)
}
private func presentSessionVerificationScreen() { private func presentSessionVerificationScreen() {
guard let sessionVerificationController = parameters.userSession.sessionVerificationController else { guard let sessionVerificationController = parameters.userSession.sessionVerificationController else {
fatalError("The sessionVerificationController should aways be valid at this point") fatalError("The sessionVerificationController should aways be valid at this point")
+30
View File
@@ -830,6 +830,8 @@ internal enum L10n {
internal static func screenAppLockSubtitleWrongPin(_ p1: Int) -> String { internal static func screenAppLockSubtitleWrongPin(_ p1: Int) -> String {
return L10n.tr("Localizable", "screen_app_lock_subtitle_wrong_pin", p1) return L10n.tr("Localizable", "screen_app_lock_subtitle_wrong_pin", p1)
} }
/// You have no blocked users
internal static var screenBlockedUsersEmpty: String { return L10n.tr("Localizable", "screen_blocked_users_empty") }
/// Unblock /// Unblock
internal static var screenBlockedUsersUnblockAlertAction: String { return L10n.tr("Localizable", "screen_blocked_users_unblock_alert_action") } internal static var screenBlockedUsersUnblockAlertAction: String { return L10n.tr("Localizable", "screen_blocked_users_unblock_alert_action") }
/// You'll be able to see all messages from them again. /// You'll be able to see all messages from them again.
@@ -1205,6 +1207,34 @@ internal enum L10n {
internal static var screenRoomAttachmentSourcePoll: String { return L10n.tr("Localizable", "screen_room_attachment_source_poll") } internal static var screenRoomAttachmentSourcePoll: String { return L10n.tr("Localizable", "screen_room_attachment_source_poll") }
/// Text Formatting /// Text Formatting
internal static var screenRoomAttachmentTextFormatting: String { return L10n.tr("Localizable", "screen_room_attachment_text_formatting") } internal static var screenRoomAttachmentTextFormatting: String { return L10n.tr("Localizable", "screen_room_attachment_text_formatting") }
/// Admins only
internal static var screenRoomChangePermissionsAdministrators: String { return L10n.tr("Localizable", "screen_room_change_permissions_administrators") }
/// Ban people
internal static var screenRoomChangePermissionsBanPeople: String { return L10n.tr("Localizable", "screen_room_change_permissions_ban_people") }
/// Delete messages
internal static var screenRoomChangePermissionsDeleteMessages: String { return L10n.tr("Localizable", "screen_room_change_permissions_delete_messages") }
/// Everyone
internal static var screenRoomChangePermissionsEveryone: String { return L10n.tr("Localizable", "screen_room_change_permissions_everyone") }
/// Invite people
internal static var screenRoomChangePermissionsInvitePeople: String { return L10n.tr("Localizable", "screen_room_change_permissions_invite_people") }
/// Member moderation
internal static var screenRoomChangePermissionsMemberModeration: String { return L10n.tr("Localizable", "screen_room_change_permissions_member_moderation") }
/// Messages and content
internal static var screenRoomChangePermissionsMessagesAndContent: String { return L10n.tr("Localizable", "screen_room_change_permissions_messages_and_content") }
/// Admins and moderators
internal static var screenRoomChangePermissionsModerators: String { return L10n.tr("Localizable", "screen_room_change_permissions_moderators") }
/// Remove people
internal static var screenRoomChangePermissionsRemovePeople: String { return L10n.tr("Localizable", "screen_room_change_permissions_remove_people") }
/// Change Room Avatar
internal static var screenRoomChangePermissionsRoomAvatar: String { return L10n.tr("Localizable", "screen_room_change_permissions_room_avatar") }
/// Room details
internal static var screenRoomChangePermissionsRoomDetails: String { return L10n.tr("Localizable", "screen_room_change_permissions_room_details") }
/// Change Room Name
internal static var screenRoomChangePermissionsRoomName: String { return L10n.tr("Localizable", "screen_room_change_permissions_room_name") }
/// Change Room Topic
internal static var screenRoomChangePermissionsRoomTopic: String { return L10n.tr("Localizable", "screen_room_change_permissions_room_topic") }
/// Send messages
internal static var screenRoomChangePermissionsSendMessages: String { return L10n.tr("Localizable", "screen_room_change_permissions_send_messages") }
/// You will not be able to undo this action. You are promoting the user to have the same power level as you. /// You will not be able to undo this action. You are promoting the user to have the same power level as you.
internal static var screenRoomChangeRoleConfirmAddAdminDescription: String { return L10n.tr("Localizable", "screen_room_change_role_confirm_add_admin_description") } internal static var screenRoomChangeRoleConfirmAddAdminDescription: String { return L10n.tr("Localizable", "screen_room_change_role_confirm_add_admin_description") }
/// Add Admin? /// Add Admin?
@@ -1892,16 +1892,16 @@ class RoomProxyMock: RoomProxyProtocol {
var name: String? var name: String?
var topic: String? var topic: String?
var avatarURL: URL? var avatarURL: URL?
var members: CurrentValuePublisher<[RoomMemberProxyProtocol], Never> { var membersPublisher: CurrentValuePublisher<[RoomMemberProxyProtocol], Never> {
get { return underlyingMembers } get { return underlyingMembersPublisher }
set(value) { underlyingMembers = value } set(value) { underlyingMembersPublisher = value }
} }
var underlyingMembers: CurrentValuePublisher<[RoomMemberProxyProtocol], Never>! var underlyingMembersPublisher: CurrentValuePublisher<[RoomMemberProxyProtocol], Never>!
var typingMembers: CurrentValuePublisher<[String], Never> { var typingMembersPublisher: CurrentValuePublisher<[String], Never> {
get { return underlyingTypingMembers } get { return underlyingTypingMembersPublisher }
set(value) { underlyingTypingMembers = value } set(value) { underlyingTypingMembersPublisher = value }
} }
var underlyingTypingMembers: CurrentValuePublisher<[String], Never>! var underlyingTypingMembersPublisher: CurrentValuePublisher<[String], Never>!
var joinedMembersCount: Int { var joinedMembersCount: Int {
get { return underlyingJoinedMembersCount } get { return underlyingJoinedMembersCount }
set(value) { underlyingJoinedMembersCount = value } set(value) { underlyingJoinedMembersCount = value }
@@ -1912,11 +1912,11 @@ class RoomProxyMock: RoomProxyProtocol {
set(value) { underlyingActiveMembersCount = value } set(value) { underlyingActiveMembersCount = value }
} }
var underlyingActiveMembersCount: Int! var underlyingActiveMembersCount: Int!
var actions: AnyPublisher<RoomProxyAction, Never> { var actionsPublisher: AnyPublisher<RoomProxyAction, Never> {
get { return underlyingActions } get { return underlyingActionsPublisher }
set(value) { underlyingActions = value } set(value) { underlyingActionsPublisher = value }
} }
var underlyingActions: AnyPublisher<RoomProxyAction, Never>! var underlyingActionsPublisher: AnyPublisher<RoomProxyAction, Never>!
var timeline: TimelineProxyProtocol { var timeline: TimelineProxyProtocol {
get { return underlyingTimeline } get { return underlyingTimeline }
set(value) { underlyingTimeline = value } set(value) { underlyingTimeline = value }
+3 -3
View File
@@ -69,15 +69,15 @@ extension RoomProxyMock {
ownUserID = configuration.ownUserID ownUserID = configuration.ownUserID
members = CurrentValueSubject(configuration.members).asCurrentValuePublisher() membersPublisher = CurrentValueSubject(configuration.members).asCurrentValuePublisher()
typingMembers = CurrentValueSubject([]).asCurrentValuePublisher() typingMembersPublisher = CurrentValueSubject([]).asCurrentValuePublisher()
joinedMembersCount = configuration.members.filter { $0.membership == .join }.count joinedMembersCount = configuration.members.filter { $0.membership == .join }.count
activeMembersCount = configuration.members.filter { $0.membership == .join || $0.membership == .invite }.count activeMembersCount = configuration.members.filter { $0.membership == .join || $0.membership == .invite }.count
updateMembersClosure = { } updateMembersClosure = { }
acceptInvitationClosure = { .success(()) } acceptInvitationClosure = { .success(()) }
underlyingActions = Empty(completeImmediately: false).eraseToAnyPublisher() underlyingActionsPublisher = Empty(completeImmediately: false).eraseToAnyPublisher()
setNameClosure = { _ in .success(()) } setNameClosure = { _ in .success(()) }
setTopicClosure = { _ in .success(()) } setTopicClosure = { _ in .success(()) }
getMemberUserIDReturnValue = .success(configuration.memberForID) getMemberUserIDReturnValue = .success(configuration.memberForID)
@@ -210,6 +210,7 @@ enum A11yIdentifiers {
let screenLock = "settings-screen_lock" let screenLock = "settings-screen_lock"
let reportBug = "settings-report_bug" let reportBug = "settings-report_bug"
let about = "settings_about" let about = "settings_about"
let blockedUsers = "settings_blocked-users"
let advancedSettings = "settings_advanced-settings" let advancedSettings = "settings_advanced-settings"
let developerOptions = "settings_developer-options" let developerOptions = "settings_developer-options"
let logout = "settings-logout" let logout = "settings-logout"
+3
View File
@@ -53,6 +53,7 @@ enum UserAvatarSizeOnScreen {
case readReceiptSheet case readReceiptSheet
case editUserDetails case editUserDetails
case suggestions case suggestions
case blockedUsers
var value: CGFloat { var value: CGFloat {
switch self { switch self {
@@ -66,6 +67,8 @@ enum UserAvatarSizeOnScreen {
return 32 return 32
case .suggestions: case .suggestions:
return 32 return 32
case .blockedUsers:
return 32
case .settings: case .settings:
return 52 return 52
case .roomDetails: case .roomDetails:
@@ -0,0 +1,49 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
import SwiftUI
struct BlockedUsersScreenCoordinatorParameters {
let clientProxy: ClientProxyProtocol
let userIndicatorController: UserIndicatorControllerProtocol
}
enum BlockedUsersScreenCoordinatorAction { }
final class BlockedUsersScreenCoordinator: CoordinatorProtocol {
private let viewModel: BlockedUsersScreenViewModelProtocol
private var cancellables = Set<AnyCancellable>()
private let actionsSubject: PassthroughSubject<BlockedUsersScreenCoordinatorAction, Never> = .init()
var actionsPublisher: AnyPublisher<BlockedUsersScreenCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(parameters: BlockedUsersScreenCoordinatorParameters) {
viewModel = BlockedUsersScreenViewModel(clientProxy: parameters.clientProxy,
userIndicatorController: parameters.userIndicatorController)
}
func stop() {
viewModel.stop()
}
func toPresentable() -> AnyView {
AnyView(BlockedUsersScreen(context: viewModel.context))
}
}
@@ -0,0 +1,39 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
enum BlockedUsersScreenViewModelAction { }
struct BlockedUsersScreenViewState: BindableState {
var blockedUsers: [String]
var processingUserID: String?
var bindings = BlockedUsersScreenViewStateBindings()
}
struct BlockedUsersScreenViewStateBindings {
var alertInfo: AlertInfo<BlockedUsersScreenViewStateAlertType>?
}
enum BlockedUsersScreenViewAction {
case unblockUser(userID: String)
}
enum BlockedUsersScreenViewStateAlertType: Hashable {
case unblock
case error
}
@@ -0,0 +1,103 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
import SwiftUI
typealias BlockedUsersScreenViewModelType = StateStoreViewModel<BlockedUsersScreenViewState, BlockedUsersScreenViewAction>
class BlockedUsersScreenViewModel: BlockedUsersScreenViewModelType, BlockedUsersScreenViewModelProtocol {
let clientProxy: ClientProxyProtocol
let userIndicatorController: UserIndicatorControllerProtocol
private let actionsSubject: PassthroughSubject<BlockedUsersScreenViewModelAction, Never> = .init()
var actionsPublisher: AnyPublisher<BlockedUsersScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(clientProxy: ClientProxyProtocol,
userIndicatorController: UserIndicatorControllerProtocol) {
self.clientProxy = clientProxy
self.userIndicatorController = userIndicatorController
super.init(initialViewState: BlockedUsersScreenViewState(blockedUsers: clientProxy.ignoredUsersPublisher.value ?? []))
showLoadingIndicator()
clientProxy.ignoredUsersPublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] blockedUsers in
guard let self else { return }
if let blockedUsers {
hideLoadingIndicator()
state.blockedUsers = blockedUsers
}
}
.store(in: &cancellables)
}
// MARK: - Public
override func process(viewAction: BlockedUsersScreenViewAction) {
switch viewAction {
case .unblockUser(let userID):
state.bindings.alertInfo = .init(id: .unblock,
title: L10n.screenBlockedUsersUnblockAlertTitle,
message: L10n.screenBlockedUsersUnblockAlertDescription,
primaryButton: .init(title: L10n.screenBlockedUsersUnblockAlertAction, role: .destructive) { [weak self] in
self?.unblockUser(userID)
},
secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil))
}
}
func stop() {
hideLoadingIndicator()
}
// MARK: - Private
private func unblockUser(_ userID: String) {
showLoadingIndicator()
state.processingUserID = userID
Task {
if case .failure = await clientProxy.unignoreUser(userID) {
state.bindings.alertInfo = .init(id: .error)
}
state.processingUserID = nil
hideLoadingIndicator()
}
}
// MARK: Loading indicator
private static let loadingIndicatorIdentifier = "BlockedUsersLoading"
private func showLoadingIndicator() {
userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,
type: .modal(progress: .indeterminate, interactiveDismissDisabled: false, allowsInteraction: true),
title: L10n.commonLoading,
persistent: true),
delay: .milliseconds(100))
}
private func hideLoadingIndicator() {
userIndicatorController.retractIndicatorWithId(Self.loadingIndicatorIdentifier)
}
}
@@ -0,0 +1,25 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
@MainActor
protocol BlockedUsersScreenViewModelProtocol {
var actionsPublisher: AnyPublisher<BlockedUsersScreenViewModelAction, Never> { get }
var context: BlockedUsersScreenViewModelType.Context { get }
func stop()
}
@@ -0,0 +1,73 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Compound
import SwiftUI
struct BlockedUsersScreen: View {
@ObservedObject var context: BlockedUsersScreenViewModel.Context
var body: some View {
content
.compoundList()
.navigationBarTitleDisplayMode(.inline)
.navigationTitle(L10n.commonBlockedUsers)
.alert(item: $context.alertInfo)
.disabled(context.viewState.processingUserID != nil)
}
// MARK: - Private
@ViewBuilder
private var content: some View {
if context.viewState.blockedUsers.isEmpty {
Text(L10n.screenBlockedUsersEmpty)
.font(.compound.bodyMD)
.foregroundColor(.compound.textSecondary)
.frame(maxWidth: .infinity, maxHeight: .infinity)
} else {
Form {
ForEach(context.viewState.blockedUsers, id: \.self) { userID in
ListRow(label: .avatar(title: userID, icon: avatar(for: userID)),
details: .isWaiting(context.viewState.processingUserID == userID),
kind: .button(action: { context.send(viewAction: .unblockUser(userID: userID)) }))
}
}
}
}
private func avatar(for userID: String) -> some View {
LoadableAvatarImage(url: nil,
name: String(userID.dropFirst()),
contentID: userID,
avatarSize: .user(on: .blockedUsers),
imageProvider: nil)
.accessibilityHidden(true)
}
}
// MARK: - Previews
struct BlockedUsersScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = BlockedUsersScreenViewModel(clientProxy: MockClientProxy(userID: RoomMemberProxyMock.mockMe.userID),
userIndicatorController: UserIndicatorControllerMock())
static var previews: some View {
NavigationStack {
BlockedUsersScreen(context: viewModel.context)
}
}
}
@@ -27,7 +27,7 @@ final class CompletionSuggestionService: CompletionSuggestionServiceProtocol {
init(roomProxy: RoomProxyProtocol) { init(roomProxy: RoomProxyProtocol) {
self.roomProxy = roomProxy self.roomProxy = roomProxy
suggestionsPublisher = suggestionTriggerSubject suggestionsPublisher = suggestionTriggerSubject
.combineLatest(roomProxy.members) .combineLatest(roomProxy.membersPublisher)
.map { [weak self] suggestionPattern, members -> [SuggestionItem] in .map { [weak self] suggestionPattern, members -> [SuggestionItem] in
guard let self, guard let self,
let suggestionPattern else { let suggestionPattern else {
@@ -54,12 +54,12 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
super.init(initialViewState: .init(userID: userSession.userID), super.init(initialViewState: .init(userID: userSession.userID),
imageProvider: userSession.mediaProvider) imageProvider: userSession.mediaProvider)
userSession.clientProxy.userAvatarURL userSession.clientProxy.userAvatarURLPublisher
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.weakAssign(to: \.state.userAvatarURL, on: self) .weakAssign(to: \.state.userAvatarURL, on: self)
.store(in: &cancellables) .store(in: &cancellables)
userSession.clientProxy.userDisplayName userSession.clientProxy.userDisplayNamePublisher
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.weakAssign(to: \.state.userDisplayName, on: self) .weakAssign(to: \.state.userDisplayName, on: self)
.store(in: &cancellables) .store(in: &cancellables)
@@ -256,7 +256,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
MXLog.info("Account not migrated, setting view room list mode to \"\(state.roomListMode)\"") MXLog.info("Account not migrated, setting view room list mode to \"\(state.roomListMode)\"")
migrationCancellable = userSession.clientProxy.callbacks migrationCancellable = userSession.clientProxy.actionsPublisher
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] callback in .sink { [weak self] callback in
guard let self, case .receivedSyncUpdate = callback else { return } guard let self, case .receivedSyncUpdate = callback else { return }
@@ -118,7 +118,7 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr
hideLoader() hideLoader()
} }
roomProxy.members roomProxy.membersPublisher
.filter { !$0.isEmpty } .filter { !$0.isEmpty }
.first() .first()
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
@@ -132,7 +132,7 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
// MARK: - Private // MARK: - Private
private func setupRoomSubscription() { private func setupRoomSubscription() {
roomProxy.actions roomProxy.actionsPublisher
.filter { $0 == .roomInfoUpdate } .filter { $0 == .roomInfoUpdate }
.throttle(for: .milliseconds(200), scheduler: DispatchQueue.main, latest: true) .throttle(for: .milliseconds(200), scheduler: DispatchQueue.main, latest: true)
.sink { [weak self] _ in .sink { [weak self] _ in
@@ -167,7 +167,7 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
return return
} }
roomProxy.members roomProxy.membersPublisher
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] members in .sink { [weak self] members in
guard let self else { return } guard let self else { return }
@@ -80,7 +80,7 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
hideLoader() hideLoader()
} }
roomProxy.members roomProxy.membersPublisher
.filter { !$0.isEmpty } .filter { !$0.isEmpty }
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] members in .sink { [weak self] members in
@@ -31,12 +31,12 @@ class RoomRolesAndPermissionsScreenViewModel: RoomRolesAndPermissionsScreenViewM
self.roomProxy = roomProxy self.roomProxy = roomProxy
super.init(initialViewState: RoomRolesAndPermissionsScreenViewState()) super.init(initialViewState: RoomRolesAndPermissionsScreenViewState())
roomProxy.members.sink { [weak self] members in roomProxy.membersPublisher.sink { [weak self] members in
self?.updateMembers(members) self?.updateMembers(members)
} }
.store(in: &cancellables) .store(in: &cancellables)
updateMembers(roomProxy.members.value) updateMembers(roomProxy.membersPublisher.value)
} }
// MARK: - Public // MARK: - Public
@@ -292,7 +292,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
.store(in: &cancellables) .store(in: &cancellables)
roomProxy roomProxy
.actions .actionsPublisher
.filter { $0 == .roomInfoUpdate } .filter { $0 == .roomInfoUpdate }
.throttle(for: .seconds(1), scheduler: DispatchQueue.main, latest: true) .throttle(for: .seconds(1), scheduler: DispatchQueue.main, latest: true)
.sink { [weak self] _ in .sink { [weak self] _ in
@@ -319,7 +319,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
.weakAssign(to: \.state.showReadReceipts, on: self) .weakAssign(to: \.state.showReadReceipts, on: self)
.store(in: &cancellables) .store(in: &cancellables)
roomProxy.members roomProxy.membersPublisher
.map { members in .map { members in
members.reduce(into: [String: RoomMemberState]()) { dictionary, member in members.reduce(into: [String: RoomMemberState]()) { dictionary, member in
dictionary[member.userID] = RoomMemberState(displayName: member.displayName, avatarURL: member.avatarURL) dictionary[member.userID] = RoomMemberState(displayName: member.displayName, avatarURL: member.avatarURL)
@@ -329,7 +329,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
.weakAssign(to: \.state.members, on: self) .weakAssign(to: \.state.members, on: self)
.store(in: &cancellables) .store(in: &cancellables)
roomProxy.typingMembers roomProxy.typingMembersPublisher
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.filter { [weak self] _ in self?.appSettings.sharePresence ?? false } .filter { [weak self] _ in self?.appSettings.sharePresence ?? false }
.weakAssign(to: \.state.typingMembers, on: self) .weakAssign(to: \.state.typingMembers, on: self)
@@ -32,6 +32,7 @@ enum SettingsScreenCoordinatorAction {
case appLock case appLock
case bugReport case bugReport
case about case about
case blockedUsers
case sessionVerification case sessionVerification
case accountSessions case accountSessions
case notifications case notifications
@@ -74,6 +75,8 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
actionsSubject.send(.bugReport) actionsSubject.send(.bugReport)
case .about: case .about:
actionsSubject.send(.about) actionsSubject.send(.about)
case .blockedUsers:
actionsSubject.send(.blockedUsers)
case .sessionVerification: case .sessionVerification:
actionsSubject.send(.sessionVerification) actionsSubject.send(.sessionVerification)
case .secureBackup: case .secureBackup:
@@ -25,6 +25,7 @@ enum SettingsScreenViewModelAction {
case appLock case appLock
case reportBug case reportBug
case about case about
case blockedUsers
case sessionVerification case sessionVerification
case secureBackup case secureBackup
case accountSessionsList case accountSessionsList
@@ -61,6 +62,7 @@ enum SettingsScreenViewAction {
case appLock case appLock
case reportBug case reportBug
case about case about
case blockedUsers
case sessionVerification case sessionVerification
case secureBackup case secureBackup
case accountSessionsList case accountSessionsList
@@ -34,12 +34,12 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
showDeveloperOptions: appSettings.isDevelopmentBuild), showDeveloperOptions: appSettings.isDevelopmentBuild),
imageProvider: userSession.mediaProvider) imageProvider: userSession.mediaProvider)
userSession.clientProxy.userAvatarURL userSession.clientProxy.userAvatarURLPublisher
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.weakAssign(to: \.state.userAvatarURL, on: self) .weakAssign(to: \.state.userAvatarURL, on: self)
.store(in: &cancellables) .store(in: &cancellables)
userSession.clientProxy.userDisplayName userSession.clientProxy.userDisplayNamePublisher
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.weakAssign(to: \.state.userDisplayName, on: self) .weakAssign(to: \.state.userDisplayName, on: self)
.store(in: &cancellables) .store(in: &cancellables)
@@ -94,6 +94,8 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
actionsSubject.send(.reportBug) actionsSubject.send(.reportBug)
case .about: case .about:
actionsSubject.send(.about) actionsSubject.send(.about)
case .blockedUsers:
actionsSubject.send(.blockedUsers)
case .logout: case .logout:
actionsSubject.send(.logout) actionsSubject.send(.logout)
case .sessionVerification: case .sessionVerification:
@@ -143,6 +143,13 @@ struct SettingsScreen: View {
context.send(viewAction: .about) context.send(viewAction: .about)
}) })
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.about) .accessibilityIdentifier(A11yIdentifiers.settingsScreen.about)
ListRow(label: .default(title: L10n.commonBlockedUsers,
icon: \.block),
kind: .navigationLink {
context.send(viewAction: .blockedUsers)
})
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.blockedUsers)
} }
} }
@@ -38,22 +38,22 @@ class UserDetailsEditScreenViewModel: UserDetailsEditScreenViewModelType, UserDe
super.init(initialViewState: UserDetailsEditScreenViewState(userID: clientProxy.userID, super.init(initialViewState: UserDetailsEditScreenViewState(userID: clientProxy.userID,
bindings: .init()), imageProvider: mediaProvider) bindings: .init()), imageProvider: mediaProvider)
clientProxy.userAvatarURL clientProxy.userAvatarURLPublisher
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.weakAssign(to: \.state.currentAvatarURL, on: self) .weakAssign(to: \.state.currentAvatarURL, on: self)
.store(in: &cancellables) .store(in: &cancellables)
clientProxy.userAvatarURL clientProxy.userAvatarURLPublisher
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.weakAssign(to: \.state.selectedAvatarURL, on: self) .weakAssign(to: \.state.selectedAvatarURL, on: self)
.store(in: &cancellables) .store(in: &cancellables)
clientProxy.userDisplayName clientProxy.userDisplayNamePublisher
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.weakAssign(to: \.state.currentDisplayName, on: self) .weakAssign(to: \.state.currentDisplayName, on: self)
.store(in: &cancellables) .store(in: &cancellables)
clientProxy.userDisplayName clientProxy.userDisplayNamePublisher
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] displayName in .sink { [weak self] displayName in
guard let self else { return } guard let self else { return }
@@ -38,6 +38,9 @@ class ClientProxy: ClientProxyProtocol {
// periphery: ignore - only for retain // periphery: ignore - only for retain
private var syncServiceStateUpdateTaskHandle: TaskHandle? private var syncServiceStateUpdateTaskHandle: TaskHandle?
// periphery:ignore - required for instance retention in the rust codebase
private var ignoredUsersListenerTaskHandle: TaskHandle?
private var delegateHandle: TaskHandle? private var delegateHandle: TaskHandle?
// These following summary providers both operate on the same allRooms() list but // These following summary providers both operate on the same allRooms() list but
@@ -69,15 +72,21 @@ class ClientProxy: ClientProxyProtocol {
private var loadCachedAvatarURLTask: Task<Void, Never>? private var loadCachedAvatarURLTask: Task<Void, Never>?
private let userAvatarURLSubject = CurrentValueSubject<URL?, Never>(nil) private let userAvatarURLSubject = CurrentValueSubject<URL?, Never>(nil)
var userAvatarURL: CurrentValuePublisher<URL?, Never> { var userAvatarURLPublisher: CurrentValuePublisher<URL?, Never> {
userAvatarURLSubject.asCurrentValuePublisher() userAvatarURLSubject.asCurrentValuePublisher()
} }
private let userDisplayNameSubject = CurrentValueSubject<String?, Never>(nil) private let userDisplayNameSubject = CurrentValueSubject<String?, Never>(nil)
var userDisplayName: CurrentValuePublisher<String?, Never> { var userDisplayNamePublisher: CurrentValuePublisher<String?, Never> {
userDisplayNameSubject.asCurrentValuePublisher() userDisplayNameSubject.asCurrentValuePublisher()
} }
private let ignoredUsersSubject = CurrentValueSubject<[String]?, Never>(nil)
var ignoredUsersPublisher: CurrentValuePublisher<[String]?, Never> {
ignoredUsersSubject
.asCurrentValuePublisher()
}
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
/// Will be `true` whilst the app cleans up and forces a logout. Prevents the sync service from restarting /// Will be `true` whilst the app cleans up and forces a logout. Prevents the sync service from restarting
@@ -92,7 +101,10 @@ class ClientProxy: ClientProxyProtocol {
} }
} }
let callbacks = PassthroughSubject<ClientProxyCallback, Never>() private let actionsSubject = PassthroughSubject<ClientProxyAction, Never>()
var actionsPublisher: AnyPublisher<ClientProxyAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
private let loadingStateSubject = CurrentValueSubject<ClientProxyLoadingState, Never>(.notLoading) private let loadingStateSubject = CurrentValueSubject<ClientProxyLoadingState, Never>(.notLoading)
var loadingStatePublisher: CurrentValuePublisher<ClientProxyLoadingState, Never> { var loadingStatePublisher: CurrentValuePublisher<ClientProxyLoadingState, Never> {
@@ -119,7 +131,7 @@ class ClientProxy: ClientProxyProtocol {
delegateHandle = client.setDelegate(delegate: ClientDelegateWrapper { [weak self] isSoftLogout in delegateHandle = client.setDelegate(delegate: ClientDelegateWrapper { [weak self] isSoftLogout in
self?.hasEncounteredAuthError = true self?.hasEncounteredAuthError = true
self?.callbacks.send(.receivedAuthError(isSoftLogout: isSoftLogout)) self?.actionsSubject.send(.receivedAuthError(isSoftLogout: isSoftLogout))
}) })
networkMonitor.reachabilityPublisher networkMonitor.reachabilityPublisher
@@ -135,6 +147,10 @@ class ClientProxy: ClientProxyProtocol {
await configureAppService() await configureAppService()
loadUserAvatarURLFromCache() loadUserAvatarURLFromCache()
ignoredUsersListenerTaskHandle = client.subscribeToIgnoredUsers(listener: IgnoredUsersListenerProxy { [weak self] ignoredUsers in
self?.ignoredUsersSubject.send(ignoredUsers)
})
} }
var userID: String { var userID: String {
@@ -472,6 +488,28 @@ class ClientProxy: ClientProxyProtocol {
} }
} }
// MARK: - Ignored users
func ignoreUser(_ userID: String) async -> Result<Void, ClientProxyError> {
do {
try await client.ignoreUser(userId: userID)
return .success(())
} catch {
MXLog.error("Failed ignoring user with error: \(error)")
return .failure(.failedIgnoringUser)
}
}
func unignoreUser(_ userID: String) async -> Result<Void, ClientProxyError> {
do {
try await client.unignoreUser(userId: userID)
return .success(())
} catch {
MXLog.error("Failed unignoring user with error: \(error)")
return .failure(.failedUnignoringUser)
}
}
// MARK: Private // MARK: Private
private func loadUserAvatarURLFromCache() { private func loadUserAvatarURLFromCache() {
@@ -570,7 +608,11 @@ class ClientProxy: ClientProxyProtocol {
} }
// Hide the sync spinner as soon as we get any update back // Hide the sync spinner as soon as we get any update back
callbacks.send(.receivedSyncUpdate) actionsSubject.send(.receivedSyncUpdate)
if ignoredUsersSubject.value == nil {
updateIgnoredUsers()
}
}) })
} }
@@ -620,6 +662,17 @@ class ClientProxy: ClientProxyProtocol {
return (nil, nil) return (nil, nil)
} }
} }
private func updateIgnoredUsers() {
Task {
do {
let ignoredUsers = try await client.ignoredUsers()
ignoredUsersSubject.send(ignoredUsers)
} catch {
MXLog.error("Failed fetching ignored users with error: \(error)")
}
}
}
} }
extension ClientProxy: MediaLoaderProtocol { extension ClientProxy: MediaLoaderProtocol {
@@ -690,3 +743,15 @@ private class ClientDelegateWrapper: ClientDelegate {
MXLog.info("Delegating session updates to the ClientSessionDelegate.") MXLog.info("Delegating session updates to the ClientSessionDelegate.")
} }
} }
private class IgnoredUsersListenerProxy: IgnoredUsersListener {
private let onUpdateClosure: ([String]) -> Void
init(onUpdateClosure: @escaping ([String]) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func call(ignoredUserIds: [String]) {
onUpdateClosure(ignoredUserIds)
}
}
@@ -18,7 +18,7 @@ import Combine
import Foundation import Foundation
import MatrixRustSDK import MatrixRustSDK
enum ClientProxyCallback { enum ClientProxyAction {
case receivedSyncUpdate case receivedSyncUpdate
case receivedAuthError(isSoftLogout: Bool) case receivedAuthError(isSoftLogout: Bool)
@@ -50,6 +50,8 @@ enum ClientProxyError: Error {
case failedGettingUserProfile case failedGettingUserProfile
case failedSettingUserAvatar case failedSettingUserAvatar
case failedCheckingIsLastDevice(Error?) case failedCheckingIsLastDevice(Error?)
case failedIgnoringUser
case failedUnignoringUser
} }
enum SlidingSyncConstants { enum SlidingSyncConstants {
@@ -70,7 +72,7 @@ struct PusherConfiguration {
} }
protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol { protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
var callbacks: PassthroughSubject<ClientProxyCallback, Never> { get } var actionsPublisher: AnyPublisher<ClientProxyAction, Never> { get }
var loadingStatePublisher: CurrentValuePublisher<ClientProxyLoadingState, Never> { get } var loadingStatePublisher: CurrentValuePublisher<ClientProxyLoadingState, Never> { get }
@@ -80,9 +82,12 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
var homeserver: String { get } var homeserver: String { get }
var userDisplayName: CurrentValuePublisher<String?, Never> { get } var userDisplayNamePublisher: CurrentValuePublisher<String?, Never> { get }
var userAvatarURL: CurrentValuePublisher<URL?, Never> { get } var userAvatarURLPublisher: CurrentValuePublisher<URL?, Never> { get }
/// We delay fetching this until after the first sync. Nil until then
var ignoredUsersPublisher: CurrentValuePublisher<[String]?, Never> { get }
var pusherNotificationClientIdentifier: String? { get } var pusherNotificationClientIdentifier: String? { get }
@@ -134,4 +139,10 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
func searchUsers(searchTerm: String, limit: UInt) async -> Result<SearchUsersResultsProxy, ClientProxyError> func searchUsers(searchTerm: String, limit: UInt) async -> Result<SearchUsersResultsProxy, ClientProxyError>
func profile(for userID: String) async -> Result<UserProfileProxy, ClientProxyError> func profile(for userID: String) async -> Result<UserProfileProxy, ClientProxyError>
// MARK: - Ignored users
func ignoreUser(_ userID: String) async -> Result<Void, ClientProxyError>
func unignoreUser(_ userID: String) async -> Result<Void, ClientProxyError>
} }
@@ -19,7 +19,10 @@ import Foundation
import MatrixRustSDK import MatrixRustSDK
class MockClientProxy: ClientProxyProtocol { class MockClientProxy: ClientProxyProtocol {
let callbacks = PassthroughSubject<ClientProxyCallback, Never>() let actionsSubject = PassthroughSubject<ClientProxyAction, Never>()
var actionsPublisher: AnyPublisher<ClientProxyAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
let loadingStatePublisher = CurrentValuePublisher<ClientProxyLoadingState, Never>(.notLoading) let loadingStatePublisher = CurrentValuePublisher<ClientProxyLoadingState, Never>(.notLoading)
@@ -34,9 +37,14 @@ class MockClientProxy: ClientProxyProtocol {
var inviteSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider() var inviteSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider()
var userAvatarURL: CurrentValuePublisher<URL?, Never> { CurrentValueSubject<URL?, Never>(nil).asCurrentValuePublisher() } var userAvatarURLPublisher: CurrentValuePublisher<URL?, Never> { CurrentValueSubject<URL?, Never>(nil).asCurrentValuePublisher() }
var userDisplayName: CurrentValuePublisher<String?, Never> { CurrentValueSubject<String?, Never>("User display name").asCurrentValuePublisher() } var userDisplayNamePublisher: CurrentValuePublisher<String?, Never> { CurrentValueSubject<String?, Never>("User display name").asCurrentValuePublisher() }
var ignoredUsersPublisher: CurrentValuePublisher<[String]?, Never> {
let ignoredUsers = [RoomMemberProxyMock].allMembers.map(\.userID)
return CurrentValueSubject<[String]?, Never>(ignoredUsers).asCurrentValuePublisher()
}
var notificationSettings: NotificationSettingsProxyProtocol = NotificationSettingsProxyMock(with: .init()) var notificationSettings: NotificationSettingsProxyProtocol = NotificationSettingsProxyMock(with: .init())
@@ -166,4 +174,12 @@ class MockClientProxy: ClientProxyProtocol {
getProfileCalled = true getProfileCalled = true
return getProfileResult return getProfileResult
} }
func ignoreUser(_ userID: String) async -> Result<Void, ClientProxyError> {
.failure(.failedIgnoringUser)
}
func unignoreUser(_ userID: String) async -> Result<Void, ClientProxyError> {
.failure(.failedUnignoringUser)
}
} }
@@ -39,17 +39,17 @@ class RoomProxy: RoomProxyProtocol {
private var subscribedForUpdates = false private var subscribedForUpdates = false
private let membersSubject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([]) private let membersSubject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([])
var members: CurrentValuePublisher<[RoomMemberProxyProtocol], Never> { var membersPublisher: CurrentValuePublisher<[RoomMemberProxyProtocol], Never> {
membersSubject.asCurrentValuePublisher() membersSubject.asCurrentValuePublisher()
} }
private let typingMembersSubject = CurrentValueSubject<[String], Never>([]) private let typingMembersSubject = CurrentValueSubject<[String], Never>([])
var typingMembers: CurrentValuePublisher<[String], Never> { var typingMembersPublisher: CurrentValuePublisher<[String], Never> {
typingMembersSubject.asCurrentValuePublisher() typingMembersSubject.asCurrentValuePublisher()
} }
private let actionsSubject = PassthroughSubject<RoomProxyAction, Never>() private let actionsSubject = PassthroughSubject<RoomProxyAction, Never>()
var actions: AnyPublisher<RoomProxyAction, Never> { var actionsPublisher: AnyPublisher<RoomProxyAction, Never> {
actionsSubject.eraseToAnyPublisher() actionsSubject.eraseToAnyPublisher()
} }
@@ -217,7 +217,7 @@ class RoomProxy: RoomProxyProtocol {
} }
func getMember(userID: String) async -> Result<RoomMemberProxyProtocol, RoomProxyError> { func getMember(userID: String) async -> Result<RoomMemberProxyProtocol, RoomProxyError> {
if let member = members.value.filter({ $0.userID == userID }).first { if let member = membersPublisher.value.filter({ $0.userID == userID }).first {
return .success(member) return .success(member)
} }
@@ -473,14 +473,13 @@ class RoomProxy: RoomProxyProtocol {
} }
private func subscribeToTypingNotifications() { private func subscribeToTypingNotifications() {
Task { typingNotificationObservationToken = room.subscribeToTypingNotifications(listener: RoomTypingNotificationUpdateListener { [weak self] typingUserIDs in
typingNotificationObservationToken = await room.subscribeToTypingNotifications(listener: RoomTypingNotificationUpdateListener { [weak self] typingUserIDs in
guard let self else { return } guard let self else { return }
MXLog.info("Received typing notification update, typingUsers: \(typingUserIDs)") MXLog.info("Received typing notification update, typingUsers: \(typingUserIDs)")
let typingMembers = typingUserIDs.compactMap { userID in let typingMembers = typingUserIDs.compactMap { userID in
if let member = self.members.value.filter({ $0.userID == userID }).first { if let member = self.membersPublisher.value.filter({ $0.userID == userID }).first {
return member.displayName ?? member.userID return member.displayName ?? member.userID
} else { } else {
return userID return userID
@@ -490,7 +489,6 @@ class RoomProxy: RoomProxyProtocol {
typingMembersSubject.send(typingMembers) typingMembersSubject.send(typingMembers)
}) })
} }
}
} }
private final class RoomInfoUpdateListener: RoomInfoListener { private final class RoomInfoUpdateListener: RoomInfoListener {
@@ -62,15 +62,15 @@ protocol RoomProxyProtocol {
var avatarURL: URL? { get } var avatarURL: URL? { get }
var members: CurrentValuePublisher<[RoomMemberProxyProtocol], Never> { get } var membersPublisher: CurrentValuePublisher<[RoomMemberProxyProtocol], Never> { get }
var typingMembers: CurrentValuePublisher<[String], Never> { get } var typingMembersPublisher: CurrentValuePublisher<[String], Never> { get }
var joinedMembersCount: Int { get } var joinedMembersCount: Int { get }
var activeMembersCount: Int { get } var activeMembersCount: Int { get }
var actions: AnyPublisher<RoomProxyAction, Never> { get } var actionsPublisher: AnyPublisher<RoomProxyAction, Never> { get }
var timeline: TimelineProxyProtocol { get } var timeline: TimelineProxyProtocol { get }
@@ -167,6 +167,6 @@ extension RoomProxyProtocol {
func members() async -> [RoomMemberProxyProtocol]? { func members() async -> [RoomMemberProxyProtocol]? {
await updateMembers() await updateMembers()
return members.value return membersPublisher.value
} }
} }
@@ -60,7 +60,7 @@ class UserSession: UserSessionProtocol {
self.mediaProvider = mediaProvider self.mediaProvider = mediaProvider
self.voiceMessageMediaManager = voiceMessageMediaManager self.voiceMessageMediaManager = voiceMessageMediaManager
clientProxy.callbacks clientProxy.actionsPublisher
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] callback in .sink { [weak self] callback in
if callback.isSyncUpdate { if callback.isSyncUpdate {
@@ -69,7 +69,7 @@ class UserSession: UserSessionProtocol {
} }
.store(in: &cancellables) .store(in: &cancellables)
authErrorCancellable = clientProxy.callbacks authErrorCancellable = clientProxy.actionsPublisher
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] callback in .sink { [weak self] callback in
guard let self else { return } guard let self else { return }
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c9fdecb8d69665bd20ce42c29a94df8fd912f1c3688207c02c2bf0ef955e80d1
size 144194
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:a0e4f20a3da82d547d5802aa2dc96db5de6520792be495069ef9c7a80168c655 oid sha256:ef88023b3f423e15d540d755d987edf4a5d7ea2580a30509d79d23ff93235c13
size 171367 size 174925
@@ -29,11 +29,12 @@ enum TemplateScreenCoordinatorAction {
final class TemplateScreenCoordinator: CoordinatorProtocol { final class TemplateScreenCoordinator: CoordinatorProtocol {
private let parameters: TemplateScreenCoordinatorParameters private let parameters: TemplateScreenCoordinatorParameters
private var viewModel: TemplateScreenViewModelProtocol private let viewModel: TemplateScreenViewModelProtocol
private let actionsSubject: PassthroughSubject<TemplateScreenCoordinatorAction, Never> = .init()
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
var actions: AnyPublisher<TemplateScreenCoordinatorAction, Never> { private let actionsSubject: PassthroughSubject<TemplateScreenCoordinatorAction, Never> = .init()
var actionsPublisher: AnyPublisher<TemplateScreenCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher() actionsSubject.eraseToAnyPublisher()
} }
@@ -44,7 +45,7 @@ final class TemplateScreenCoordinator: CoordinatorProtocol {
} }
func start() { func start() {
viewModel.actions.sink { [weak self] action in viewModel.actionsPublisher.sink { [weak self] action in
MXLog.info("Coordinator: received view model action: \(action)") MXLog.info("Coordinator: received view model action: \(action)")
guard let self else { return } guard let self else { return }
@@ -20,9 +20,8 @@ import SwiftUI
typealias TemplateScreenViewModelType = StateStoreViewModel<TemplateScreenViewState, TemplateScreenViewAction> typealias TemplateScreenViewModelType = StateStoreViewModel<TemplateScreenViewState, TemplateScreenViewAction>
class TemplateScreenViewModel: TemplateScreenViewModelType, TemplateScreenViewModelProtocol { class TemplateScreenViewModel: TemplateScreenViewModelType, TemplateScreenViewModelProtocol {
private var actionsSubject: PassthroughSubject<TemplateScreenViewModelAction, Never> = .init() private let actionsSubject: PassthroughSubject<TemplateScreenViewModelAction, Never> = .init()
var actionsPublisher: AnyPublisher<TemplateScreenViewModelAction, Never> {
var actions: AnyPublisher<TemplateScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher() actionsSubject.eraseToAnyPublisher()
} }
@@ -18,6 +18,6 @@ import Combine
@MainActor @MainActor
protocol TemplateScreenViewModelProtocol { protocol TemplateScreenViewModelProtocol {
var actions: AnyPublisher<TemplateScreenViewModelAction, Never> { get } var actionsPublisher: AnyPublisher<TemplateScreenViewModelAction, Never> { get }
var context: TemplateScreenViewModelType.Context { get } var context: TemplateScreenViewModelType.Context { get }
} }
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:d3c08ea0ffa71cf6eb554ddc394a7db01325588bb93e3ab094f18a76f669dd9e oid sha256:1e6b1f4f3a3d38a5a55557a0458c46d2ac4c44fc438230ce059fa8b323a8c8ef
size 138739 size 145116
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:25076c598482ee48360108f0acc67cde09d191a51c296afb763018bbd748291f oid sha256:13a413c5db74eb8127c9c2c0ae6d0f5633bc40f5c100f7ae679f314785af7116
size 164268 size 172723
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:400e3eba5557dd4138c7b8ff2cc305dd95e74c3e1b86f2bd5675a5c7c8dcf656 oid sha256:30bebd1f04d98e9a62567e8dc51762b8d0e25baf437a296210652a16fe522c52
size 148563 size 155955
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:e1eb3e2304004c1d0b21f8431678fdbf9c7e751c57ff4c686706d37fec9bc0d3 oid sha256:666546d6087d733ad951d2d3a64eae1f6b2a2a43962d1f724b2b1d3da43531e0
size 184449 size 190544
@@ -0,0 +1,32 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
import XCTest
@testable import ElementX
@MainActor
class BlockedUsersScreenViewModelTests: XCTestCase {
func testInitialState() {
let clientProxy = MockClientProxy(userID: RoomMemberProxyMock.mockMe.userID)
let viewModel = BlockedUsersScreenViewModel(clientProxy: clientProxy,
userIndicatorController: ServiceLocator.shared.userIndicatorController)
XCTAssertFalse(viewModel.context.viewState.blockedUsers.isEmpty)
}
}
+2 -2
View File
@@ -25,7 +25,7 @@ class PillContextTests: XCTestCase {
let id = "@test:matrix.org" let id = "@test:matrix.org"
let proxyMock = RoomProxyMock(with: .init(name: "Test")) let proxyMock = RoomProxyMock(with: .init(name: "Test"))
let subject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([]) let subject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([])
proxyMock.members = subject.asCurrentValuePublisher() proxyMock.membersPublisher = subject.asCurrentValuePublisher()
let mock = RoomScreenViewModel(roomProxy: proxyMock, let mock = RoomScreenViewModel(roomProxy: proxyMock,
timelineController: MockRoomTimelineController(), timelineController: MockRoomTimelineController(),
mediaProvider: MockMediaProvider(), mediaProvider: MockMediaProvider(),
@@ -54,7 +54,7 @@ class PillContextTests: XCTestCase {
let id = "@test:matrix.org" let id = "@test:matrix.org"
let proxyMock = RoomProxyMock(with: .init(name: "Test", ownUserID: id)) let proxyMock = RoomProxyMock(with: .init(name: "Test", ownUserID: id))
let subject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([]) let subject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([])
proxyMock.members = subject.asCurrentValuePublisher() proxyMock.membersPublisher = subject.asCurrentValuePublisher()
let mock = RoomScreenViewModel(roomProxy: proxyMock, let mock = RoomScreenViewModel(roomProxy: proxyMock,
timelineController: MockRoomTimelineController(), timelineController: MockRoomTimelineController(),
mediaProvider: MockMediaProvider(), mediaProvider: MockMediaProvider(),
@@ -396,7 +396,7 @@ class RoomScreenViewModelTests: XCTestCase {
let items: [RoomTimelineItemProtocol] = [TextRoomTimelineItem(eventID: "t1"), let items: [RoomTimelineItemProtocol] = [TextRoomTimelineItem(eventID: "t1"),
TextRoomTimelineItem(eventID: "t2"), TextRoomTimelineItem(eventID: "t2"),
SeparatorRoomTimelineItem(timelineID: "v3")] SeparatorRoomTimelineItem(timelineID: "v3")]
let (viewModel, _, timelineProxy, _, _) = readReceiptsConfiguration(with: items) let (viewModel, _, _, _, _) = readReceiptsConfiguration(with: items)
// When sending a read receipt for the last item. // When sending a read receipt for the last item.
viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(items.last!.id)) viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(items.last!.id))
@@ -43,7 +43,7 @@ final class UserSessionTests: XCTestCase {
isVerified: false, isVerified: false,
requestDelay: .zero) requestDelay: .zero)
clientProxy.sessionVerificationControllerProxyResult = .success(controller) clientProxy.sessionVerificationControllerProxyResult = .success(controller)
clientProxy.callbacks.send(.receivedSyncUpdate) clientProxy.actionsSubject.send(.receivedSyncUpdate)
waitForExpectations(timeout: 1.0) waitForExpectations(timeout: 1.0)
} }
@@ -64,7 +64,7 @@ final class UserSessionTests: XCTestCase {
} }
.store(in: &cancellables) .store(in: &cancellables)
clientProxy.callbacks.send(.receivedSyncUpdate) clientProxy.actionsSubject.send(.receivedSyncUpdate)
controller.callbacks.send(.finished) controller.callbacks.send(.finished)
waitForExpectations(timeout: 1.0) waitForExpectations(timeout: 1.0)
} }
+1
View File
@@ -0,0 +1 @@
Add a blocked users section in the app settings