mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-06 12:28:29 +00:00
Fixes #2486 - Implement blocked users management in the app settings
This commit is contained in:
@@ -217,6 +217,7 @@
|
||||
35E975CFDA60E05362A7CF79 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 1222DB76B917EB8A55365BA5 /* target.yml */; };
|
||||
366D5BFE52CB79E804C7D095 /* CallScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAD9547E47C58930E2CE8306 /* CallScreenViewModelTests.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 */; };
|
||||
36AD4DD4C798E22584ED3200 /* Emojibase in Frameworks */ = {isa = PBXBuildFile; productRef = C05729B1684C331F5FFE9232 /* Emojibase */; };
|
||||
36CD6E11B37396E14F032CB6 /* WysiwygComposer in Frameworks */ = {isa = PBXBuildFile; productRef = CA07D57389DACE18AEB6A5E2 /* WysiwygComposer */; };
|
||||
@@ -562,6 +563,7 @@
|
||||
8C1A5ECAF895D4CAF8C4D461 /* AppActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F21ED7205048668BEB44A38 /* AppActivityView.swift */; };
|
||||
8C42B5B1642D189C362A5EDF /* SecureBackupScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91831D7042EADD0CC2B5EC36 /* SecureBackupScreenUITests.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 */; };
|
||||
8D3E1FADD78E72504DE0E402 /* UserAgentBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB3B237387B8288A5A938F1B /* UserAgentBuilderTests.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 */; };
|
||||
9278EC51D24E57445B290521 /* AudioSessionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB284643AF7AB131E307DCE0 /* AudioSessionProtocol.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 */; };
|
||||
93A549135E6C027A0D823BFE /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 593FBBF394712F2963E98A0B /* DTCoreText */; };
|
||||
93BA4A81B6D893271101F9F0 /* DeviceKit in Frameworks */ = {isa = PBXBuildFile; productRef = A7CA6F33C553805035C3B114 /* DeviceKit */; };
|
||||
@@ -593,6 +596,7 @@
|
||||
94D0F36A87E596A93C0C178A /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E89E530A8E92EC44301CA1 /* Bundle.swift */; };
|
||||
95690DDD9D547D3D842ACBE3 /* AnalyticsSettingsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD371B60E07A5324B9507EF /* AnalyticsSettingsScreenCoordinator.swift */; };
|
||||
9586E90A447C4896C0CA3A8E /* TimelineItemReplyDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE89A8BD65CCE3FCC925CA14 /* TimelineItemReplyDetails.swift */; };
|
||||
95E7B236F7116CACE05A6BC9 /* BlockedUsersScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A16D0F226B1819D017531647 /* BlockedUsersScreenCoordinator.swift */; };
|
||||
962A4F8AD6312804E2C6BB6E /* PhotoLibraryPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A232D9156D225BD9FD1D0C43 /* PhotoLibraryPicker.swift */; };
|
||||
964B9D2EC38C488C360CE0C9 /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = B902EA6CD3296B0E10EE432B /* HomeScreen.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 */; };
|
||||
A494741843F087881299ACF0 /* RestorationToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3558A15CFB934F9229301527 /* RestorationToken.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 */; };
|
||||
A4E885358D7DD5A072A06824 /* PostHog in Frameworks */ = {isa = PBXBuildFile; productRef = CCE5BF78B125320CBF3BB834 /* PostHog */; };
|
||||
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 */; };
|
||||
CE6F237360875D3D573FD0B2 /* RoomNotificationSettingsProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6B522BD637845AB9570B10 /* RoomNotificationSettingsProxy.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 */; };
|
||||
CF3827071B0BC9638BD44F5D /* WaitlistScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB58EF0176D4CFB1040DA22 /* WaitlistScreenViewModel.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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -1582,6 +1589,7 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -1665,6 +1673,7 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -1673,6 +1682,7 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -1957,6 +1967,7 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -1980,6 +1991,7 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@@ -2510,6 +2522,14 @@
|
||||
path = ServerSelectionScreen;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
2E035B978E415C77423FA3C2 /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
ED60E4D2CD678E1EBF16F77A /* BlockedUsersScreen.swift */,
|
||||
);
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
2ECFF6B05DAA37EB10DBF7E8 /* UITests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -3352,6 +3372,7 @@
|
||||
C55CC239AE12339C565F6C9A /* AudioRecorderStateTests.swift */,
|
||||
2441E2424E78A40FC95DBA76 /* AudioRecorderTests.swift */,
|
||||
6DFCAA239095A116976E32C4 /* BackgroundTaskTests.swift */,
|
||||
240610DF32F3213BEC5611D7 /* BlockedUsersScreenViewModelTests.swift */,
|
||||
EFFD3200F9960D4996159F10 /* BugReportServiceTests.swift */,
|
||||
7AB7ED3A898B07976F3AA90F /* BugReportViewModelTests.swift */,
|
||||
CAD9547E47C58930E2CE8306 /* CallScreenViewModelTests.swift */,
|
||||
@@ -4623,6 +4644,7 @@
|
||||
669239C03835CD8B51E0FFDB /* AnalyticsPromptScreen */,
|
||||
13263FFEA7749D822B51AA90 /* AppLock */,
|
||||
E74CD7681375AD2EAA34D66B /* Authentication */,
|
||||
EFD4F7FCAAAB3EF45EE7A067 /* BlockedUsersScreen */,
|
||||
53FB148CD26AFB6A5B9E20B3 /* BugReportScreen */,
|
||||
1185EECDD07495D65AC84AFC /* CallScreen */,
|
||||
27F2500AC8736AAE774520C0 /* ComposerToolbar */,
|
||||
@@ -4771,6 +4793,18 @@
|
||||
path = StartChatScreen;
|
||||
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 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -5393,6 +5427,7 @@
|
||||
3042527CB344A9EF1157FC26 /* AudioRecorderStateTests.swift in Sources */,
|
||||
192A3CDCD0174AD1E4A128E4 /* AudioRecorderTests.swift in Sources */,
|
||||
0F9E38A75337D0146652ACAB /* BackgroundTaskTests.swift in Sources */,
|
||||
CEAEA57B7665C8E790599A78 /* BlockedUsersScreenViewModelTests.swift in Sources */,
|
||||
7F61F9ACD5EC9E845EF3EFBF /* BugReportServiceTests.swift in Sources */,
|
||||
C7CFDB4929DDD9A3B5BA085D /* BugReportViewModelTests.swift in Sources */,
|
||||
366D5BFE52CB79E804C7D095 /* CallScreenViewModelTests.swift in Sources */,
|
||||
@@ -5590,6 +5625,11 @@
|
||||
A4B0BAD62A12ED76BD611B79 /* BadgeView.swift in Sources */,
|
||||
38546A6010A2CF240EC9AF73 /* BindableState.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 */,
|
||||
B6DF6B6FA8734B70F9BF261E /* BlurHashDecode.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_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_blocked_users_empty" = "You have no blocked users";
|
||||
"screen_blocked_users_unblocking" = "Unblocking...";
|
||||
"screen_bug_report_attach_screenshot" = "Attach screenshot";
|
||||
"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_poll" = "Poll";
|
||||
"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_title" = "Add Admin?";
|
||||
"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_report_content_block_user" = "Block user";
|
||||
"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_leave_conversation_title" = "Leave conversation";
|
||||
"screen_room_details_leave_room_title" = "Leave room";
|
||||
|
||||
@@ -419,7 +419,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
|
||||
Task {
|
||||
let credentials = SoftLogoutScreenCredentials(userID: userSession.userID,
|
||||
homeserverName: userSession.homeserver,
|
||||
userDisplayName: userSession.clientProxy.userDisplayName.value ?? "",
|
||||
userDisplayName: userSession.clientProxy.userDisplayNamePublisher.value ?? "",
|
||||
deviceID: userSession.deviceID)
|
||||
|
||||
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
|
||||
// An SS request will time out after 30 seconds if no new data is available
|
||||
backgroundRefreshSyncObserver = userSession.clientProxy
|
||||
.callbacks
|
||||
.actionsPublisher
|
||||
.filter(\.isSyncUpdate)
|
||||
.collect(.byTimeOrCount(DispatchQueue.main, .seconds(10), 10))
|
||||
.sink(receiveValue: { [weak self] _ in
|
||||
|
||||
@@ -128,6 +128,8 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
bugReportFlowCoordinator?.start()
|
||||
case .about:
|
||||
presentLegalInformationScreen()
|
||||
case .blockedUsers:
|
||||
presentBlockedUsersScreen()
|
||||
case .sessionVerification:
|
||||
presentSessionVerificationScreen()
|
||||
case .accountSessions:
|
||||
@@ -203,6 +205,12 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
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() {
|
||||
guard let sessionVerificationController = parameters.userSession.sessionVerificationController else {
|
||||
fatalError("The sessionVerificationController should aways be valid at this point")
|
||||
|
||||
@@ -830,6 +830,8 @@ internal enum L10n {
|
||||
internal static func screenAppLockSubtitleWrongPin(_ p1: Int) -> String {
|
||||
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
|
||||
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.
|
||||
@@ -1205,6 +1207,34 @@ internal enum L10n {
|
||||
internal static var screenRoomAttachmentSourcePoll: String { return L10n.tr("Localizable", "screen_room_attachment_source_poll") }
|
||||
/// 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.
|
||||
internal static var screenRoomChangeRoleConfirmAddAdminDescription: String { return L10n.tr("Localizable", "screen_room_change_role_confirm_add_admin_description") }
|
||||
/// Add Admin?
|
||||
|
||||
@@ -1892,16 +1892,16 @@ class RoomProxyMock: RoomProxyProtocol {
|
||||
var name: String?
|
||||
var topic: String?
|
||||
var avatarURL: URL?
|
||||
var members: CurrentValuePublisher<[RoomMemberProxyProtocol], Never> {
|
||||
get { return underlyingMembers }
|
||||
set(value) { underlyingMembers = value }
|
||||
var membersPublisher: CurrentValuePublisher<[RoomMemberProxyProtocol], Never> {
|
||||
get { return underlyingMembersPublisher }
|
||||
set(value) { underlyingMembersPublisher = value }
|
||||
}
|
||||
var underlyingMembers: CurrentValuePublisher<[RoomMemberProxyProtocol], Never>!
|
||||
var typingMembers: CurrentValuePublisher<[String], Never> {
|
||||
get { return underlyingTypingMembers }
|
||||
set(value) { underlyingTypingMembers = value }
|
||||
var underlyingMembersPublisher: CurrentValuePublisher<[RoomMemberProxyProtocol], Never>!
|
||||
var typingMembersPublisher: CurrentValuePublisher<[String], Never> {
|
||||
get { return underlyingTypingMembersPublisher }
|
||||
set(value) { underlyingTypingMembersPublisher = value }
|
||||
}
|
||||
var underlyingTypingMembers: CurrentValuePublisher<[String], Never>!
|
||||
var underlyingTypingMembersPublisher: CurrentValuePublisher<[String], Never>!
|
||||
var joinedMembersCount: Int {
|
||||
get { return underlyingJoinedMembersCount }
|
||||
set(value) { underlyingJoinedMembersCount = value }
|
||||
@@ -1912,11 +1912,11 @@ class RoomProxyMock: RoomProxyProtocol {
|
||||
set(value) { underlyingActiveMembersCount = value }
|
||||
}
|
||||
var underlyingActiveMembersCount: Int!
|
||||
var actions: AnyPublisher<RoomProxyAction, Never> {
|
||||
get { return underlyingActions }
|
||||
set(value) { underlyingActions = value }
|
||||
var actionsPublisher: AnyPublisher<RoomProxyAction, Never> {
|
||||
get { return underlyingActionsPublisher }
|
||||
set(value) { underlyingActionsPublisher = value }
|
||||
}
|
||||
var underlyingActions: AnyPublisher<RoomProxyAction, Never>!
|
||||
var underlyingActionsPublisher: AnyPublisher<RoomProxyAction, Never>!
|
||||
var timeline: TimelineProxyProtocol {
|
||||
get { return underlyingTimeline }
|
||||
set(value) { underlyingTimeline = value }
|
||||
|
||||
@@ -69,15 +69,15 @@ extension RoomProxyMock {
|
||||
|
||||
ownUserID = configuration.ownUserID
|
||||
|
||||
members = CurrentValueSubject(configuration.members).asCurrentValuePublisher()
|
||||
typingMembers = CurrentValueSubject([]).asCurrentValuePublisher()
|
||||
membersPublisher = CurrentValueSubject(configuration.members).asCurrentValuePublisher()
|
||||
typingMembersPublisher = CurrentValueSubject([]).asCurrentValuePublisher()
|
||||
|
||||
joinedMembersCount = configuration.members.filter { $0.membership == .join }.count
|
||||
activeMembersCount = configuration.members.filter { $0.membership == .join || $0.membership == .invite }.count
|
||||
|
||||
updateMembersClosure = { }
|
||||
acceptInvitationClosure = { .success(()) }
|
||||
underlyingActions = Empty(completeImmediately: false).eraseToAnyPublisher()
|
||||
underlyingActionsPublisher = Empty(completeImmediately: false).eraseToAnyPublisher()
|
||||
setNameClosure = { _ in .success(()) }
|
||||
setTopicClosure = { _ in .success(()) }
|
||||
getMemberUserIDReturnValue = .success(configuration.memberForID)
|
||||
|
||||
@@ -210,6 +210,7 @@ enum A11yIdentifiers {
|
||||
let screenLock = "settings-screen_lock"
|
||||
let reportBug = "settings-report_bug"
|
||||
let about = "settings_about"
|
||||
let blockedUsers = "settings_blocked-users"
|
||||
let advancedSettings = "settings_advanced-settings"
|
||||
let developerOptions = "settings_developer-options"
|
||||
let logout = "settings-logout"
|
||||
|
||||
@@ -53,6 +53,7 @@ enum UserAvatarSizeOnScreen {
|
||||
case readReceiptSheet
|
||||
case editUserDetails
|
||||
case suggestions
|
||||
case blockedUsers
|
||||
|
||||
var value: CGFloat {
|
||||
switch self {
|
||||
@@ -66,6 +67,8 @@ enum UserAvatarSizeOnScreen {
|
||||
return 32
|
||||
case .suggestions:
|
||||
return 32
|
||||
case .blockedUsers:
|
||||
return 32
|
||||
case .settings:
|
||||
return 52
|
||||
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) {
|
||||
self.roomProxy = roomProxy
|
||||
suggestionsPublisher = suggestionTriggerSubject
|
||||
.combineLatest(roomProxy.members)
|
||||
.combineLatest(roomProxy.membersPublisher)
|
||||
.map { [weak self] suggestionPattern, members -> [SuggestionItem] in
|
||||
guard let self,
|
||||
let suggestionPattern else {
|
||||
|
||||
@@ -54,12 +54,12 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
super.init(initialViewState: .init(userID: userSession.userID),
|
||||
imageProvider: userSession.mediaProvider)
|
||||
|
||||
userSession.clientProxy.userAvatarURL
|
||||
userSession.clientProxy.userAvatarURLPublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.weakAssign(to: \.state.userAvatarURL, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
userSession.clientProxy.userDisplayName
|
||||
userSession.clientProxy.userDisplayNamePublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.weakAssign(to: \.state.userDisplayName, on: self)
|
||||
.store(in: &cancellables)
|
||||
@@ -256,7 +256,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
|
||||
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)
|
||||
.sink { [weak self] callback in
|
||||
guard let self, case .receivedSyncUpdate = callback else { return }
|
||||
|
||||
@@ -118,7 +118,7 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr
|
||||
hideLoader()
|
||||
}
|
||||
|
||||
roomProxy.members
|
||||
roomProxy.membersPublisher
|
||||
.filter { !$0.isEmpty }
|
||||
.first()
|
||||
.receive(on: DispatchQueue.main)
|
||||
|
||||
@@ -132,7 +132,7 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
||||
// MARK: - Private
|
||||
|
||||
private func setupRoomSubscription() {
|
||||
roomProxy.actions
|
||||
roomProxy.actionsPublisher
|
||||
.filter { $0 == .roomInfoUpdate }
|
||||
.throttle(for: .milliseconds(200), scheduler: DispatchQueue.main, latest: true)
|
||||
.sink { [weak self] _ in
|
||||
@@ -167,7 +167,7 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
||||
return
|
||||
}
|
||||
|
||||
roomProxy.members
|
||||
roomProxy.membersPublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] members in
|
||||
guard let self else { return }
|
||||
|
||||
@@ -80,7 +80,7 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
|
||||
hideLoader()
|
||||
}
|
||||
|
||||
roomProxy.members
|
||||
roomProxy.membersPublisher
|
||||
.filter { !$0.isEmpty }
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] members in
|
||||
|
||||
+2
-2
@@ -31,12 +31,12 @@ class RoomRolesAndPermissionsScreenViewModel: RoomRolesAndPermissionsScreenViewM
|
||||
self.roomProxy = roomProxy
|
||||
super.init(initialViewState: RoomRolesAndPermissionsScreenViewState())
|
||||
|
||||
roomProxy.members.sink { [weak self] members in
|
||||
roomProxy.membersPublisher.sink { [weak self] members in
|
||||
self?.updateMembers(members)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
updateMembers(roomProxy.members.value)
|
||||
updateMembers(roomProxy.membersPublisher.value)
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
@@ -292,7 +292,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
.store(in: &cancellables)
|
||||
|
||||
roomProxy
|
||||
.actions
|
||||
.actionsPublisher
|
||||
.filter { $0 == .roomInfoUpdate }
|
||||
.throttle(for: .seconds(1), scheduler: DispatchQueue.main, latest: true)
|
||||
.sink { [weak self] _ in
|
||||
@@ -319,7 +319,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
.weakAssign(to: \.state.showReadReceipts, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
roomProxy.members
|
||||
roomProxy.membersPublisher
|
||||
.map { members in
|
||||
members.reduce(into: [String: RoomMemberState]()) { dictionary, member in
|
||||
dictionary[member.userID] = RoomMemberState(displayName: member.displayName, avatarURL: member.avatarURL)
|
||||
@@ -329,7 +329,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
.weakAssign(to: \.state.members, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
roomProxy.typingMembers
|
||||
roomProxy.typingMembersPublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.filter { [weak self] _ in self?.appSettings.sharePresence ?? false }
|
||||
.weakAssign(to: \.state.typingMembers, on: self)
|
||||
|
||||
@@ -32,6 +32,7 @@ enum SettingsScreenCoordinatorAction {
|
||||
case appLock
|
||||
case bugReport
|
||||
case about
|
||||
case blockedUsers
|
||||
case sessionVerification
|
||||
case accountSessions
|
||||
case notifications
|
||||
@@ -74,6 +75,8 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
|
||||
actionsSubject.send(.bugReport)
|
||||
case .about:
|
||||
actionsSubject.send(.about)
|
||||
case .blockedUsers:
|
||||
actionsSubject.send(.blockedUsers)
|
||||
case .sessionVerification:
|
||||
actionsSubject.send(.sessionVerification)
|
||||
case .secureBackup:
|
||||
|
||||
@@ -25,6 +25,7 @@ enum SettingsScreenViewModelAction {
|
||||
case appLock
|
||||
case reportBug
|
||||
case about
|
||||
case blockedUsers
|
||||
case sessionVerification
|
||||
case secureBackup
|
||||
case accountSessionsList
|
||||
@@ -61,6 +62,7 @@ enum SettingsScreenViewAction {
|
||||
case appLock
|
||||
case reportBug
|
||||
case about
|
||||
case blockedUsers
|
||||
case sessionVerification
|
||||
case secureBackup
|
||||
case accountSessionsList
|
||||
|
||||
@@ -34,12 +34,12 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
|
||||
showDeveloperOptions: appSettings.isDevelopmentBuild),
|
||||
imageProvider: userSession.mediaProvider)
|
||||
|
||||
userSession.clientProxy.userAvatarURL
|
||||
userSession.clientProxy.userAvatarURLPublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.weakAssign(to: \.state.userAvatarURL, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
userSession.clientProxy.userDisplayName
|
||||
userSession.clientProxy.userDisplayNamePublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.weakAssign(to: \.state.userDisplayName, on: self)
|
||||
.store(in: &cancellables)
|
||||
@@ -94,6 +94,8 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
|
||||
actionsSubject.send(.reportBug)
|
||||
case .about:
|
||||
actionsSubject.send(.about)
|
||||
case .blockedUsers:
|
||||
actionsSubject.send(.blockedUsers)
|
||||
case .logout:
|
||||
actionsSubject.send(.logout)
|
||||
case .sessionVerification:
|
||||
|
||||
@@ -143,6 +143,13 @@ struct SettingsScreen: View {
|
||||
context.send(viewAction: .about)
|
||||
})
|
||||
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.about)
|
||||
|
||||
ListRow(label: .default(title: L10n.commonBlockedUsers,
|
||||
icon: \.block),
|
||||
kind: .navigationLink {
|
||||
context.send(viewAction: .blockedUsers)
|
||||
})
|
||||
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.blockedUsers)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -38,22 +38,22 @@ class UserDetailsEditScreenViewModel: UserDetailsEditScreenViewModelType, UserDe
|
||||
super.init(initialViewState: UserDetailsEditScreenViewState(userID: clientProxy.userID,
|
||||
bindings: .init()), imageProvider: mediaProvider)
|
||||
|
||||
clientProxy.userAvatarURL
|
||||
clientProxy.userAvatarURLPublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.weakAssign(to: \.state.currentAvatarURL, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
clientProxy.userAvatarURL
|
||||
clientProxy.userAvatarURLPublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.weakAssign(to: \.state.selectedAvatarURL, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
clientProxy.userDisplayName
|
||||
clientProxy.userDisplayNamePublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.weakAssign(to: \.state.currentDisplayName, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
clientProxy.userDisplayName
|
||||
clientProxy.userDisplayNamePublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] displayName in
|
||||
guard let self else { return }
|
||||
|
||||
@@ -38,6 +38,9 @@ class ClientProxy: ClientProxyProtocol {
|
||||
// periphery: ignore - only for retain
|
||||
private var syncServiceStateUpdateTaskHandle: TaskHandle?
|
||||
|
||||
// periphery:ignore - required for instance retention in the rust codebase
|
||||
private var ignoredUsersListenerTaskHandle: TaskHandle?
|
||||
|
||||
private var delegateHandle: TaskHandle?
|
||||
|
||||
// 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 let userAvatarURLSubject = CurrentValueSubject<URL?, Never>(nil)
|
||||
var userAvatarURL: CurrentValuePublisher<URL?, Never> {
|
||||
var userAvatarURLPublisher: CurrentValuePublisher<URL?, Never> {
|
||||
userAvatarURLSubject.asCurrentValuePublisher()
|
||||
}
|
||||
|
||||
private let userDisplayNameSubject = CurrentValueSubject<String?, Never>(nil)
|
||||
var userDisplayName: CurrentValuePublisher<String?, Never> {
|
||||
var userDisplayNamePublisher: CurrentValuePublisher<String?, Never> {
|
||||
userDisplayNameSubject.asCurrentValuePublisher()
|
||||
}
|
||||
|
||||
private let ignoredUsersSubject = CurrentValueSubject<[String]?, Never>(nil)
|
||||
var ignoredUsersPublisher: CurrentValuePublisher<[String]?, Never> {
|
||||
ignoredUsersSubject
|
||||
.asCurrentValuePublisher()
|
||||
}
|
||||
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
/// 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)
|
||||
var loadingStatePublisher: CurrentValuePublisher<ClientProxyLoadingState, Never> {
|
||||
@@ -119,7 +131,7 @@ class ClientProxy: ClientProxyProtocol {
|
||||
|
||||
delegateHandle = client.setDelegate(delegate: ClientDelegateWrapper { [weak self] isSoftLogout in
|
||||
self?.hasEncounteredAuthError = true
|
||||
self?.callbacks.send(.receivedAuthError(isSoftLogout: isSoftLogout))
|
||||
self?.actionsSubject.send(.receivedAuthError(isSoftLogout: isSoftLogout))
|
||||
})
|
||||
|
||||
networkMonitor.reachabilityPublisher
|
||||
@@ -135,6 +147,10 @@ class ClientProxy: ClientProxyProtocol {
|
||||
await configureAppService()
|
||||
|
||||
loadUserAvatarURLFromCache()
|
||||
|
||||
ignoredUsersListenerTaskHandle = client.subscribeToIgnoredUsers(listener: IgnoredUsersListenerProxy { [weak self] ignoredUsers in
|
||||
self?.ignoredUsersSubject.send(ignoredUsers)
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
private func loadUserAvatarURLFromCache() {
|
||||
@@ -570,7 +608,11 @@ class ClientProxy: ClientProxyProtocol {
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -690,3 +743,15 @@ private class ClientDelegateWrapper: ClientDelegate {
|
||||
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 MatrixRustSDK
|
||||
|
||||
enum ClientProxyCallback {
|
||||
enum ClientProxyAction {
|
||||
case receivedSyncUpdate
|
||||
case receivedAuthError(isSoftLogout: Bool)
|
||||
|
||||
@@ -50,6 +50,8 @@ enum ClientProxyError: Error {
|
||||
case failedGettingUserProfile
|
||||
case failedSettingUserAvatar
|
||||
case failedCheckingIsLastDevice(Error?)
|
||||
case failedIgnoringUser
|
||||
case failedUnignoringUser
|
||||
}
|
||||
|
||||
enum SlidingSyncConstants {
|
||||
@@ -70,7 +72,7 @@ struct PusherConfiguration {
|
||||
}
|
||||
|
||||
protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
|
||||
var callbacks: PassthroughSubject<ClientProxyCallback, Never> { get }
|
||||
var actionsPublisher: AnyPublisher<ClientProxyAction, Never> { get }
|
||||
|
||||
var loadingStatePublisher: CurrentValuePublisher<ClientProxyLoadingState, Never> { get }
|
||||
|
||||
@@ -80,9 +82,12 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
|
||||
|
||||
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 }
|
||||
|
||||
@@ -134,4 +139,10 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
|
||||
func searchUsers(searchTerm: String, limit: UInt) async -> Result<SearchUsersResultsProxy, 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
|
||||
|
||||
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)
|
||||
|
||||
@@ -34,9 +37,14 @@ class MockClientProxy: ClientProxyProtocol {
|
||||
|
||||
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())
|
||||
|
||||
@@ -166,4 +174,12 @@ class MockClientProxy: ClientProxyProtocol {
|
||||
getProfileCalled = true
|
||||
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 let membersSubject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([])
|
||||
var members: CurrentValuePublisher<[RoomMemberProxyProtocol], Never> {
|
||||
var membersPublisher: CurrentValuePublisher<[RoomMemberProxyProtocol], Never> {
|
||||
membersSubject.asCurrentValuePublisher()
|
||||
}
|
||||
|
||||
private let typingMembersSubject = CurrentValueSubject<[String], Never>([])
|
||||
var typingMembers: CurrentValuePublisher<[String], Never> {
|
||||
var typingMembersPublisher: CurrentValuePublisher<[String], Never> {
|
||||
typingMembersSubject.asCurrentValuePublisher()
|
||||
}
|
||||
|
||||
private let actionsSubject = PassthroughSubject<RoomProxyAction, Never>()
|
||||
var actions: AnyPublisher<RoomProxyAction, Never> {
|
||||
var actionsPublisher: AnyPublisher<RoomProxyAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ class RoomProxy: RoomProxyProtocol {
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -473,23 +473,21 @@ class RoomProxy: RoomProxyProtocol {
|
||||
}
|
||||
|
||||
private func subscribeToTypingNotifications() {
|
||||
Task {
|
||||
typingNotificationObservationToken = await room.subscribeToTypingNotifications(listener: RoomTypingNotificationUpdateListener { [weak self] typingUserIDs in
|
||||
guard let self else { return }
|
||||
typingNotificationObservationToken = room.subscribeToTypingNotifications(listener: RoomTypingNotificationUpdateListener { [weak self] typingUserIDs in
|
||||
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
|
||||
if let member = self.members.value.filter({ $0.userID == userID }).first {
|
||||
return member.displayName ?? member.userID
|
||||
} else {
|
||||
return userID
|
||||
}
|
||||
let typingMembers = typingUserIDs.compactMap { userID in
|
||||
if let member = self.membersPublisher.value.filter({ $0.userID == userID }).first {
|
||||
return member.displayName ?? member.userID
|
||||
} else {
|
||||
return userID
|
||||
}
|
||||
}
|
||||
|
||||
typingMembersSubject.send(typingMembers)
|
||||
})
|
||||
}
|
||||
typingMembersSubject.send(typingMembers)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,15 +62,15 @@ protocol RoomProxyProtocol {
|
||||
|
||||
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 activeMembersCount: Int { get }
|
||||
|
||||
var actions: AnyPublisher<RoomProxyAction, Never> { get }
|
||||
var actionsPublisher: AnyPublisher<RoomProxyAction, Never> { get }
|
||||
|
||||
var timeline: TimelineProxyProtocol { get }
|
||||
|
||||
@@ -167,6 +167,6 @@ extension RoomProxyProtocol {
|
||||
|
||||
func members() async -> [RoomMemberProxyProtocol]? {
|
||||
await updateMembers()
|
||||
return members.value
|
||||
return membersPublisher.value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ class UserSession: UserSessionProtocol {
|
||||
self.mediaProvider = mediaProvider
|
||||
self.voiceMessageMediaManager = voiceMessageMediaManager
|
||||
|
||||
clientProxy.callbacks
|
||||
clientProxy.actionsPublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] callback in
|
||||
if callback.isSyncUpdate {
|
||||
@@ -69,7 +69,7 @@ class UserSession: UserSessionProtocol {
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
authErrorCancellable = clientProxy.callbacks
|
||||
authErrorCancellable = clientProxy.actionsPublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] callback in
|
||||
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
|
||||
oid sha256:a0e4f20a3da82d547d5802aa2dc96db5de6520792be495069ef9c7a80168c655
|
||||
size 171367
|
||||
oid sha256:ef88023b3f423e15d540d755d987edf4a5d7ea2580a30509d79d23ff93235c13
|
||||
size 174925
|
||||
|
||||
+5
-4
@@ -29,11 +29,12 @@ enum TemplateScreenCoordinatorAction {
|
||||
|
||||
final class TemplateScreenCoordinator: CoordinatorProtocol {
|
||||
private let parameters: TemplateScreenCoordinatorParameters
|
||||
private var viewModel: TemplateScreenViewModelProtocol
|
||||
private let actionsSubject: PassthroughSubject<TemplateScreenCoordinatorAction, Never> = .init()
|
||||
private let viewModel: TemplateScreenViewModelProtocol
|
||||
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
var actions: AnyPublisher<TemplateScreenCoordinatorAction, Never> {
|
||||
private let actionsSubject: PassthroughSubject<TemplateScreenCoordinatorAction, Never> = .init()
|
||||
var actionsPublisher: AnyPublisher<TemplateScreenCoordinatorAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
@@ -44,7 +45,7 @@ final class TemplateScreenCoordinator: CoordinatorProtocol {
|
||||
}
|
||||
|
||||
func start() {
|
||||
viewModel.actions.sink { [weak self] action in
|
||||
viewModel.actionsPublisher.sink { [weak self] action in
|
||||
MXLog.info("Coordinator: received view model action: \(action)")
|
||||
|
||||
guard let self else { return }
|
||||
|
||||
@@ -20,9 +20,8 @@ import SwiftUI
|
||||
typealias TemplateScreenViewModelType = StateStoreViewModel<TemplateScreenViewState, TemplateScreenViewAction>
|
||||
|
||||
class TemplateScreenViewModel: TemplateScreenViewModelType, TemplateScreenViewModelProtocol {
|
||||
private var actionsSubject: PassthroughSubject<TemplateScreenViewModelAction, Never> = .init()
|
||||
|
||||
var actions: AnyPublisher<TemplateScreenViewModelAction, Never> {
|
||||
private let actionsSubject: PassthroughSubject<TemplateScreenViewModelAction, Never> = .init()
|
||||
var actionsPublisher: AnyPublisher<TemplateScreenViewModelAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -18,6 +18,6 @@ import Combine
|
||||
|
||||
@MainActor
|
||||
protocol TemplateScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<TemplateScreenViewModelAction, Never> { get }
|
||||
var actionsPublisher: AnyPublisher<TemplateScreenViewModelAction, Never> { get }
|
||||
var context: TemplateScreenViewModelType.Context { get }
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d3c08ea0ffa71cf6eb554ddc394a7db01325588bb93e3ab094f18a76f669dd9e
|
||||
size 138739
|
||||
oid sha256:1e6b1f4f3a3d38a5a55557a0458c46d2ac4c44fc438230ce059fa8b323a8c8ef
|
||||
size 145116
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:25076c598482ee48360108f0acc67cde09d191a51c296afb763018bbd748291f
|
||||
size 164268
|
||||
oid sha256:13a413c5db74eb8127c9c2c0ae6d0f5633bc40f5c100f7ae679f314785af7116
|
||||
size 172723
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:400e3eba5557dd4138c7b8ff2cc305dd95e74c3e1b86f2bd5675a5c7c8dcf656
|
||||
size 148563
|
||||
oid sha256:30bebd1f04d98e9a62567e8dc51762b8d0e25baf437a296210652a16fe522c52
|
||||
size 155955
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e1eb3e2304004c1d0b21f8431678fdbf9c7e751c57ff4c686706d37fec9bc0d3
|
||||
size 184449
|
||||
oid sha256:666546d6087d733ad951d2d3a64eae1f6b2a2a43962d1f724b2b1d3da43531e0
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ class PillContextTests: XCTestCase {
|
||||
let id = "@test:matrix.org"
|
||||
let proxyMock = RoomProxyMock(with: .init(name: "Test"))
|
||||
let subject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([])
|
||||
proxyMock.members = subject.asCurrentValuePublisher()
|
||||
proxyMock.membersPublisher = subject.asCurrentValuePublisher()
|
||||
let mock = RoomScreenViewModel(roomProxy: proxyMock,
|
||||
timelineController: MockRoomTimelineController(),
|
||||
mediaProvider: MockMediaProvider(),
|
||||
@@ -54,7 +54,7 @@ class PillContextTests: XCTestCase {
|
||||
let id = "@test:matrix.org"
|
||||
let proxyMock = RoomProxyMock(with: .init(name: "Test", ownUserID: id))
|
||||
let subject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([])
|
||||
proxyMock.members = subject.asCurrentValuePublisher()
|
||||
proxyMock.membersPublisher = subject.asCurrentValuePublisher()
|
||||
let mock = RoomScreenViewModel(roomProxy: proxyMock,
|
||||
timelineController: MockRoomTimelineController(),
|
||||
mediaProvider: MockMediaProvider(),
|
||||
|
||||
@@ -396,7 +396,7 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
let items: [RoomTimelineItemProtocol] = [TextRoomTimelineItem(eventID: "t1"),
|
||||
TextRoomTimelineItem(eventID: "t2"),
|
||||
SeparatorRoomTimelineItem(timelineID: "v3")]
|
||||
let (viewModel, _, timelineProxy, _, _) = readReceiptsConfiguration(with: items)
|
||||
let (viewModel, _, _, _, _) = readReceiptsConfiguration(with: items)
|
||||
|
||||
// When sending a read receipt for the last item.
|
||||
viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(items.last!.id))
|
||||
|
||||
@@ -43,7 +43,7 @@ final class UserSessionTests: XCTestCase {
|
||||
isVerified: false,
|
||||
requestDelay: .zero)
|
||||
clientProxy.sessionVerificationControllerProxyResult = .success(controller)
|
||||
clientProxy.callbacks.send(.receivedSyncUpdate)
|
||||
clientProxy.actionsSubject.send(.receivedSyncUpdate)
|
||||
waitForExpectations(timeout: 1.0)
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ final class UserSessionTests: XCTestCase {
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
clientProxy.callbacks.send(.receivedSyncUpdate)
|
||||
clientProxy.actionsSubject.send(.receivedSyncUpdate)
|
||||
controller.callbacks.send(.finished)
|
||||
waitForExpectations(timeout: 1.0)
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Add a blocked users section in the app settings
|
||||
Reference in New Issue
Block a user