mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-07 04:48:34 +00:00
User verification state indicators (#3793)
* Introduce a `UserIdentityProxy` and have it combine upstream methods into an easy to digest `UserIdentityVerificationState`. Use it in a dedicated `VerificationBadge` UI component * Show a DMs counterpart verification state in the room header * Show a warning on the room details `People` entry when there are identity verification state violations on any of the members. * Show verification badges in the room member list * Show a withdraw verification section on the room member details for users that have pinning violations. * Remove the verification section from the profile screen as there's no reliable way to keep it up to date - the underlying Rust SDK Olm Machine can be rebuilt without notice which would break any existing user identity change streams. * Update preview test snapshots
This commit is contained in:
@@ -106,6 +106,7 @@
|
||||
126CBCF5B0145FA1377C1316 /* Tracing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83B574805B9812C111D6215D /* Tracing.swift */; };
|
||||
126EE01D8BEAEF26105D83C5 /* RoomDetailsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A5FEF17ED7E6176D922D4F /* RoomDetailsScreen.swift */; };
|
||||
128FFD8A3D85845F9A927F47 /* PollRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF8548D48512127CCC17C520 /* PollRoomTimelineView.swift */; };
|
||||
12AF926E783E40BFB32A2D84 /* UserIdentityProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8536B40F578BA7FDB23D97B1 /* UserIdentityProxyMock.swift */; };
|
||||
12C867E85E6D12EEDFD0B127 /* CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96C4762F8D6112E43117DB2F /* CustomStringConvertible.swift */; };
|
||||
12CCA59536EDD99A3272CF77 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC3F82523D6F48B926D6AF68 /* AppSettings.swift */; };
|
||||
12CD8B5CC30A05061228BF9E /* TimelineItemMenuActionProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E6065FC6BC4A1B4C629E08 /* TimelineItemMenuActionProvider.swift */; };
|
||||
@@ -188,6 +189,7 @@
|
||||
238D561CA231339C6D4D06F3 /* ClientBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1C33355FFB0F0953C35036 /* ClientBuilder.swift */; };
|
||||
241CDEFE23819867D9B39066 /* RoomChangePermissionsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE75941583A033A9EDC9FE0 /* RoomChangePermissionsScreenViewModel.swift */; };
|
||||
244407B18B2F2D6466BA5961 /* RoomChangeRolesScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82DFA1B7B088D033E0794B82 /* RoomChangeRolesScreenCoordinator.swift */; };
|
||||
2447FADEF13225BB6227B977 /* VerificationBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1D97BAF04AA150C0EF03021 /* VerificationBadge.swift */; };
|
||||
244CB93DD7390379D905AFA8 /* DeactivateAccountScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E49D10BFA7E4D70A947888C /* DeactivateAccountScreen.swift */; };
|
||||
24A1BBADAC43DC3F3A7347DA /* AnalyticsPromptScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E53BFB7E4F329621C844E8C3 /* AnalyticsPromptScreen.swift */; };
|
||||
24A75F72EEB7561B82D726FD /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2141693488CE5446BB391964 /* Date.swift */; };
|
||||
@@ -401,6 +403,7 @@
|
||||
4E4EF97B9F9CEFAC726BA72F /* TimelineProviderMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EACAFB3F3E017060F9F1C5 /* TimelineProviderMock.swift */; };
|
||||
4E8A2A2CFEB212F14E49E1A1 /* AppLockSetupSettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5484457C81B325660901B161 /* AppLockSetupSettingsScreen.swift */; };
|
||||
4E945AD6862C403F74E57755 /* RoomTimelineItemFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 105B2A8426404EF66F00CFDB /* RoomTimelineItemFactory.swift */; };
|
||||
4E9782D683660463804C2DC3 /* UserIdentityProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F793C422BDACE0C60C774F4 /* UserIdentityProxyProtocol.swift */; };
|
||||
4EA1CE0E88EA68E862FF0EA2 /* NotificationSettingsEditScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B564D748B67A156F413CD97 /* NotificationSettingsEditScreenModels.swift */; };
|
||||
4EAC427267424192964B16B3 /* AppSettingsHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13BE9781699FB510E9263192 /* AppSettingsHook.swift */; };
|
||||
4ED764A24F2A715C25CF07F1 /* TimelineMediaPreviewFileExportPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30856520F3263D0E195710D7 /* TimelineMediaPreviewFileExportPicker.swift */; };
|
||||
@@ -619,6 +622,7 @@
|
||||
7A0D335D38ECA095A575B4F7 /* TimelineStyler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DB0E533508094156D8024C3 /* TimelineStyler.swift */; };
|
||||
7A170A5A4A352954BB2A1B96 /* AuthenticationStartScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24E8C8817F59BEC7E358EB78 /* AuthenticationStartScreen.swift */; };
|
||||
7A25D6926A2C01DB8D0D67A5 /* BadgeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A028783CFFF861C5E44FFB1 /* BadgeLabel.swift */; };
|
||||
7A495A5F3E5522DD7928CF8F /* UserIdentityProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 964093C7CA8823CAB7FFD88E /* UserIdentityProxy.swift */; };
|
||||
7A642EE5F1ADC5D520F21924 /* MediaProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85EB16E7FE59A947CA441531 /* MediaProviderProtocol.swift */; };
|
||||
7A71AEF419904209BB8C2833 /* UserAgentBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F2529D434C750ED78ADF1ED /* UserAgentBuilder.swift */; };
|
||||
7A8B264506D3DDABC01B4EEB /* AppMediator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53AC78E49A297AC1D72A7CF /* AppMediator.swift */; };
|
||||
@@ -993,7 +997,6 @@
|
||||
C76892321558E75101E68ED6 /* ReadableFrameModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398817652FA8ABAE0A31AC6D /* ReadableFrameModifier.swift */; };
|
||||
C7774720A4B2E34693E3227C /* RoomNotificationSettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8896CDD20CA2D87EA3B848A1 /* RoomNotificationSettingsScreen.swift */; };
|
||||
C7ABEBECDC513F7887DACF66 /* ProgressMaskModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68010886142843705E342645 /* ProgressMaskModifier.swift */; };
|
||||
C7B07EBA0F12B5912DA9BB97 /* UserIdentitySDKMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8DE9D0D480D087D0F676B52 /* UserIdentitySDKMock.swift */; };
|
||||
C7F20DBF873CC72FB482E326 /* test_rotated_image.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 723B055A57857BFF0F18D9CB /* test_rotated_image.jpg */; };
|
||||
C80E06ED97CE52704A46C148 /* ClientBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1C33355FFB0F0953C35036 /* ClientBuilder.swift */; };
|
||||
C85C7A201E4CFDA477ACEBEB /* AppLockSetupSettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8610C1D21565C950BCA6A454 /* AppLockSetupSettingsScreenViewModelProtocol.swift */; };
|
||||
@@ -1414,6 +1417,7 @@
|
||||
0F5567A7EF6F2AB9473236F6 /* DocumentPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentPicker.swift; sourceTree = "<group>"; };
|
||||
0F569CFB77E0D40BD82203D9 /* AuthenticationClientBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationClientBuilder.swift; sourceTree = "<group>"; };
|
||||
0F64447FF544298A6A3BEF85 /* NotificationSettingsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsScreenModels.swift; sourceTree = "<group>"; };
|
||||
0F793C422BDACE0C60C774F4 /* UserIdentityProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIdentityProxyProtocol.swift; sourceTree = "<group>"; };
|
||||
0FA60F848D1C14F873F9621A /* RoomMemberDetailsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
105429F29096729EDD3152CF /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/SAS.strings; sourceTree = "<group>"; };
|
||||
1059E2AE7878CF7820592637 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
@@ -1967,6 +1971,7 @@
|
||||
8512B82404B1751D0BCC82D2 /* MediaEventsTimelineScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaEventsTimelineScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
85149F56BA333619900E2410 /* UserDetailsEditScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDetailsEditScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
851B95BB98649B8E773D6790 /* AppLockService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockService.swift; sourceTree = "<group>"; };
|
||||
8536B40F578BA7FDB23D97B1 /* UserIdentityProxyMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIdentityProxyMock.swift; sourceTree = "<group>"; };
|
||||
8544F7058D31DBEB8DBFF0F5 /* NotificationSettingsEditScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||
854BCEAF2A832176FAACD2CB /* SplashScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
85666E40F7E817809B4FD787 /* ComposerToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerToolbar.swift; sourceTree = "<group>"; };
|
||||
@@ -2047,6 +2052,7 @@
|
||||
955336CBD5ED73C792D1F580 /* EncryptionAuthenticity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionAuthenticity.swift; sourceTree = "<group>"; };
|
||||
95A2E4BD7C0CAD25EF924A4C /* GeneratedPreviewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneratedPreviewTests.swift; sourceTree = "<group>"; };
|
||||
95BAC0F6C9644336E9567EE6 /* NSRegularExpresion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSRegularExpresion.swift; sourceTree = "<group>"; };
|
||||
964093C7CA8823CAB7FFD88E /* UserIdentityProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIdentityProxy.swift; sourceTree = "<group>"; };
|
||||
969694F67E844FCA51F7E051 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
96C4762F8D6112E43117DB2F /* CustomStringConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomStringConvertible.swift; sourceTree = "<group>"; };
|
||||
96CE9D6642DD487D8CC90C9C /* landscape_test_image.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = landscape_test_image.jpg; sourceTree = "<group>"; };
|
||||
@@ -2340,6 +2346,7 @@
|
||||
D196116D2DD3F2757D45FCB7 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/SAS.strings; sourceTree = "<group>"; };
|
||||
D1BC84BA0AF11C2128D58ABD /* Common.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Common.swift; sourceTree = "<group>"; };
|
||||
D1D8479BB704B7EF696F8ABE /* RoomPollsHistoryScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPollsHistoryScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
D1D97BAF04AA150C0EF03021 /* VerificationBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationBadge.swift; sourceTree = "<group>"; };
|
||||
D263254AFE5B7993FFBBF324 /* NSE.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NSE.entitlements; sourceTree = "<group>"; };
|
||||
D26813CCE39221FE30BF22CD /* PlatformViewVersionPredicate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformViewVersionPredicate.swift; sourceTree = "<group>"; };
|
||||
D28F7A6CEEA4A2815B0F0F55 /* SettingsFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsFlowCoordinator.swift; sourceTree = "<group>"; };
|
||||
@@ -2444,7 +2451,6 @@
|
||||
E8A1F98AE670377B20679FF5 /* MediaPlayerProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayerProvider.swift; sourceTree = "<group>"; };
|
||||
E8AE4B3273BA189FDCD4055C /* UserIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicator.swift; sourceTree = "<group>"; };
|
||||
E8CA187FE656EE5A3F6C7DE5 /* UIFont+AttributedStringBuilder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIFont+AttributedStringBuilder.m"; sourceTree = "<group>"; };
|
||||
E8DE9D0D480D087D0F676B52 /* UserIdentitySDKMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIdentitySDKMock.swift; sourceTree = "<group>"; };
|
||||
E96ED747FF90332EA1333C22 /* RoomTimelineItemFixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemFixtures.swift; sourceTree = "<group>"; };
|
||||
E992D7B8BE54B2AB454613AF /* XCUIElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCUIElement.swift; sourceTree = "<group>"; };
|
||||
E9A3D3CFA199FA7897364547 /* CallInviteRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallInviteRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||
@@ -2707,6 +2713,7 @@
|
||||
children = (
|
||||
693E16574C6F7F9FA1015A8C /* Search.swift */,
|
||||
832397B5C3D00A4BF52C5F0B /* ShouldScrollOnKeyboardDidShow.swift */,
|
||||
D1D97BAF04AA150C0EF03021 /* VerificationBadge.swift */,
|
||||
E2DA161C142B7AB8CC40F752 /* Animation */,
|
||||
CE2FBFD64A89F5DBE4EB30DB /* Layout */,
|
||||
E6E1D07163F8752D62DA4A93 /* Styles */,
|
||||
@@ -3215,6 +3222,7 @@
|
||||
B0F5CC38803B8382D2C63222 /* TimelineControllerFactoryMock.swift */,
|
||||
62EACAFB3F3E017060F9F1C5 /* TimelineProviderMock.swift */,
|
||||
17A8AA0DFA06012A9DAB951E /* TimelineProxyMock.swift */,
|
||||
8536B40F578BA7FDB23D97B1 /* UserIdentityProxyMock.swift */,
|
||||
7893780A1FD6E3F38B3E9049 /* UserIndicatorControllerMock.swift */,
|
||||
AAD01F7FC2BBAC7351948595 /* UserProfile+Mock.swift */,
|
||||
F4469F6AE311BDC439B3A5EC /* UserSessionMock.swift */,
|
||||
@@ -5652,6 +5660,8 @@
|
||||
D071F86CD47582B9196C9D16 /* UserDiscoverySection.swift */,
|
||||
65AAD845E53B0C8B5E0812C2 /* UserDiscoveryService.swift */,
|
||||
7F615A00DB223FF3280204D2 /* UserDiscoveryServiceProtocol.swift */,
|
||||
964093C7CA8823CAB7FFD88E /* UserIdentityProxy.swift */,
|
||||
0F793C422BDACE0C60C774F4 /* UserIdentityProxyProtocol.swift */,
|
||||
51C454AE59914B551A6D02C0 /* UserProfileProxy.swift */,
|
||||
);
|
||||
path = Users;
|
||||
@@ -5717,7 +5727,6 @@
|
||||
children = (
|
||||
8EAF4A49F3ACD8BB8B0D2371 /* ClientSDKMock.swift */,
|
||||
5EFB1D29B0870AFB6A56E9B8 /* IdentityResetHandleSDKMock.swift */,
|
||||
E8DE9D0D480D087D0F676B52 /* UserIdentitySDKMock.swift */,
|
||||
);
|
||||
path = SDK;
|
||||
sourceTree = "<group>";
|
||||
@@ -7624,7 +7633,9 @@
|
||||
828EA5009557C2B9DCD4CA0F /* UserDiscoverySection.swift in Sources */,
|
||||
044DD8F80231BC30570F7965 /* UserDiscoveryService.swift in Sources */,
|
||||
1C409A26A99F0371C47AFA51 /* UserDiscoveryServiceProtocol.swift in Sources */,
|
||||
C7B07EBA0F12B5912DA9BB97 /* UserIdentitySDKMock.swift in Sources */,
|
||||
7A495A5F3E5522DD7928CF8F /* UserIdentityProxy.swift in Sources */,
|
||||
12AF926E783E40BFB32A2D84 /* UserIdentityProxyMock.swift in Sources */,
|
||||
4E9782D683660463804C2DC3 /* UserIdentityProxyProtocol.swift in Sources */,
|
||||
988BA75A182738150894A23F /* UserIndicator.swift in Sources */,
|
||||
C4E0D03DF88242697545A9B7 /* UserIndicatorController.swift in Sources */,
|
||||
3467FEE8210D301FF1B77001 /* UserIndicatorControllerMock.swift in Sources */,
|
||||
@@ -7651,6 +7662,7 @@
|
||||
7E91BAC17963ED41208F489B /* UserSessionStore.swift in Sources */,
|
||||
79D57E9AE03A2DC689D14EA2 /* UserSessionStoreMock.swift in Sources */,
|
||||
AC69B6DF15FC451AB2945036 /* UserSessionStoreProtocol.swift in Sources */,
|
||||
2447FADEF13225BB6227B977 /* VerificationBadge.swift in Sources */,
|
||||
5C33976A720B64094CBC56B1 /* VideoMediaEventsTimelineView.swift in Sources */,
|
||||
F07D88421A9BC4D03D4A5055 /* VideoRoomTimelineItem.swift in Sources */,
|
||||
1A83DD22F3E6F76B13B6E2F9 /* VideoRoomTimelineItemContent.swift in Sources */,
|
||||
@@ -8509,7 +8521,7 @@
|
||||
repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift";
|
||||
requirement = {
|
||||
kind = exactVersion;
|
||||
version = 25.02.07;
|
||||
version = 25.02.11;
|
||||
};
|
||||
};
|
||||
701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = {
|
||||
|
||||
@@ -149,8 +149,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/element-hq/matrix-rust-components-swift",
|
||||
"state" : {
|
||||
"revision" : "bc819f09ac66bbe1adc2fde2afeb7ab023d1b909",
|
||||
"version" : "25.2.7"
|
||||
"revision" : "cc010fc6971370d1df2c0eb67cc5cfd577465b62",
|
||||
"version" : "25.2.11"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -889,8 +889,9 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
}
|
||||
|
||||
private func presentRoomMembersList() {
|
||||
let parameters = RoomMembersListScreenCoordinatorParameters(mediaProvider: userSession.mediaProvider,
|
||||
let parameters = RoomMembersListScreenCoordinatorParameters(clientProxy: userSession.clientProxy,
|
||||
roomProxy: roomProxy,
|
||||
mediaProvider: userSession.mediaProvider,
|
||||
userIndicatorController: userIndicatorController,
|
||||
analytics: analytics)
|
||||
let coordinator = RoomMembersListScreenCoordinator(parameters: parameters)
|
||||
@@ -1275,8 +1276,6 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
stateMachine.tryEvent(.startChildFlow(roomID: roomID, via: [], entryPoint: .room))
|
||||
case .startCall(let roomID):
|
||||
Task { await self.presentCallScreen(roomID: roomID) }
|
||||
case .verifyUser(let userID):
|
||||
actionsSubject.send(.verifyUser(userID: userID))
|
||||
case .dismiss:
|
||||
break // Not supported when pushed.
|
||||
}
|
||||
|
||||
@@ -923,8 +923,6 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
stateMachine.processEvent(.selectRoom(roomID: roomID, via: [], entryPoint: .room))
|
||||
case .startCall(let roomID):
|
||||
Task { await self.presentCallScreen(roomID: roomID, notifyOtherParticipants: false) }
|
||||
case .verifyUser(let userID):
|
||||
presentSessionVerificationScreen(flow: .userIntiator(userID: userID))
|
||||
case .dismiss:
|
||||
navigationSplitCoordinator.setSheetCoordinator(nil)
|
||||
}
|
||||
|
||||
@@ -95,6 +95,6 @@ extension ClientProxyMock {
|
||||
return await .joined(JoinedRoomProxyMock(.init(id: room.id, name: room.name)))
|
||||
}
|
||||
|
||||
userIdentityForReturnValue = .success(UserIdentitySDKMock(configuration: .init()))
|
||||
userIdentityForReturnValue = .success(UserIdentityProxyMock(configuration: .init()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4860,13 +4860,13 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable {
|
||||
var userIdentityForReceivedUserID: String?
|
||||
var userIdentityForReceivedInvocations: [String] = []
|
||||
|
||||
var userIdentityForUnderlyingReturnValue: Result<UserIdentity?, ClientProxyError>!
|
||||
var userIdentityForReturnValue: Result<UserIdentity?, ClientProxyError>! {
|
||||
var userIdentityForUnderlyingReturnValue: Result<UserIdentityProxyProtocol?, ClientProxyError>!
|
||||
var userIdentityForReturnValue: Result<UserIdentityProxyProtocol?, ClientProxyError>! {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return userIdentityForUnderlyingReturnValue
|
||||
} else {
|
||||
var returnValue: Result<UserIdentity?, ClientProxyError>? = nil
|
||||
var returnValue: Result<UserIdentityProxyProtocol?, ClientProxyError>? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = userIdentityForUnderlyingReturnValue
|
||||
}
|
||||
@@ -4884,9 +4884,9 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable {
|
||||
}
|
||||
}
|
||||
}
|
||||
var userIdentityForClosure: ((String) async -> Result<UserIdentity?, ClientProxyError>)?
|
||||
var userIdentityForClosure: ((String) async -> Result<UserIdentityProxyProtocol?, ClientProxyError>)?
|
||||
|
||||
func userIdentity(for userID: String) async -> Result<UserIdentity?, ClientProxyError> {
|
||||
func userIdentity(for userID: String) async -> Result<UserIdentityProxyProtocol?, ClientProxyError> {
|
||||
userIdentityForCallsCount += 1
|
||||
userIdentityForReceivedUserID = userID
|
||||
DispatchQueue.main.async {
|
||||
@@ -16511,6 +16511,14 @@ class UserDiscoveryServiceMock: UserDiscoveryServiceProtocol, @unchecked Sendabl
|
||||
}
|
||||
}
|
||||
}
|
||||
class UserIdentityProxyMock: UserIdentityProxyProtocol, @unchecked Sendable {
|
||||
var verificationState: UserIdentityVerificationState {
|
||||
get { return underlyingVerificationState }
|
||||
set(value) { underlyingVerificationState = value }
|
||||
}
|
||||
var underlyingVerificationState: UserIdentityVerificationState!
|
||||
|
||||
}
|
||||
class UserIndicatorControllerMock: UserIndicatorControllerProtocol, @unchecked Sendable {
|
||||
var window: UIWindow?
|
||||
var alertInfo: AlertInfo<UUID>?
|
||||
|
||||
@@ -22876,6 +22876,71 @@ open class UserIdentitySDKMock: MatrixRustSDK.UserIdentity, @unchecked Sendable
|
||||
|
||||
fileprivate var pointer: UnsafeMutableRawPointer!
|
||||
|
||||
//MARK: - hasVerificationViolation
|
||||
|
||||
var hasVerificationViolationUnderlyingCallsCount = 0
|
||||
open var hasVerificationViolationCallsCount: Int {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return hasVerificationViolationUnderlyingCallsCount
|
||||
} else {
|
||||
var returnValue: Int? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = hasVerificationViolationUnderlyingCallsCount
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
}
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
hasVerificationViolationUnderlyingCallsCount = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
hasVerificationViolationUnderlyingCallsCount = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
open var hasVerificationViolationCalled: Bool {
|
||||
return hasVerificationViolationCallsCount > 0
|
||||
}
|
||||
|
||||
var hasVerificationViolationUnderlyingReturnValue: Bool!
|
||||
open var hasVerificationViolationReturnValue: Bool! {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return hasVerificationViolationUnderlyingReturnValue
|
||||
} else {
|
||||
var returnValue: Bool? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = hasVerificationViolationUnderlyingReturnValue
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
}
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
hasVerificationViolationUnderlyingReturnValue = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
hasVerificationViolationUnderlyingReturnValue = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
open var hasVerificationViolationClosure: (() -> Bool)?
|
||||
|
||||
open override func hasVerificationViolation() -> Bool {
|
||||
hasVerificationViolationCallsCount += 1
|
||||
if let hasVerificationViolationClosure = hasVerificationViolationClosure {
|
||||
return hasVerificationViolationClosure()
|
||||
} else {
|
||||
return hasVerificationViolationReturnValue
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - isVerified
|
||||
|
||||
var isVerifiedUnderlyingCallsCount = 0
|
||||
@@ -23046,6 +23111,71 @@ open class UserIdentitySDKMock: MatrixRustSDK.UserIdentity, @unchecked Sendable
|
||||
try await pinClosure?()
|
||||
}
|
||||
|
||||
//MARK: - wasPreviouslyVerified
|
||||
|
||||
var wasPreviouslyVerifiedUnderlyingCallsCount = 0
|
||||
open var wasPreviouslyVerifiedCallsCount: Int {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return wasPreviouslyVerifiedUnderlyingCallsCount
|
||||
} else {
|
||||
var returnValue: Int? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = wasPreviouslyVerifiedUnderlyingCallsCount
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
}
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
wasPreviouslyVerifiedUnderlyingCallsCount = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
wasPreviouslyVerifiedUnderlyingCallsCount = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
open var wasPreviouslyVerifiedCalled: Bool {
|
||||
return wasPreviouslyVerifiedCallsCount > 0
|
||||
}
|
||||
|
||||
var wasPreviouslyVerifiedUnderlyingReturnValue: Bool!
|
||||
open var wasPreviouslyVerifiedReturnValue: Bool! {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return wasPreviouslyVerifiedUnderlyingReturnValue
|
||||
} else {
|
||||
var returnValue: Bool? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = wasPreviouslyVerifiedUnderlyingReturnValue
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
}
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
wasPreviouslyVerifiedUnderlyingReturnValue = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
wasPreviouslyVerifiedUnderlyingReturnValue = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
open var wasPreviouslyVerifiedClosure: (() -> Bool)?
|
||||
|
||||
open override func wasPreviouslyVerified() -> Bool {
|
||||
wasPreviouslyVerifiedCallsCount += 1
|
||||
if let wasPreviouslyVerifiedClosure = wasPreviouslyVerifiedClosure {
|
||||
return wasPreviouslyVerifiedClosure()
|
||||
} else {
|
||||
return wasPreviouslyVerifiedReturnValue
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - withdrawVerification
|
||||
|
||||
open var withdrawVerificationThrowableError: Error?
|
||||
|
||||
+4
-7
@@ -1,21 +1,18 @@
|
||||
//
|
||||
// Copyright 2024 New Vector Ltd.
|
||||
// Copyright 2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
extension UserIdentitySDKMock {
|
||||
extension UserIdentityProxyMock {
|
||||
struct Configuration {
|
||||
var isVerified = false
|
||||
var verificationState: UserIdentityVerificationState = .notVerified
|
||||
}
|
||||
|
||||
convenience init(configuration: Configuration) {
|
||||
self.init()
|
||||
|
||||
isVerifiedReturnValue = configuration.isVerified
|
||||
underlyingVerificationState = configuration.verificationState
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Copyright 2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Compound
|
||||
import SwiftUI
|
||||
|
||||
struct VerificationBadge: View {
|
||||
let verificationState: UserIdentityVerificationState
|
||||
|
||||
var body: some View {
|
||||
switch verificationState {
|
||||
case .verified:
|
||||
CompoundIcon(\.verified, size: .small, relativeTo: .compound.bodyMD)
|
||||
.foregroundStyle(.compound.iconSuccessPrimary)
|
||||
case .verificationViolation:
|
||||
CompoundIcon(\.infoSolid, size: .small, relativeTo: .compound.bodyMD)
|
||||
.foregroundStyle(.compound.iconCriticalPrimary)
|
||||
case .notVerified:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct VerificationBadge_Previews: PreviewProvider, TestablePreview {
|
||||
static var previews: some View {
|
||||
VStack(spacing: 16.0) {
|
||||
VerificationBadge(verificationState: .notVerified)
|
||||
VerificationBadge(verificationState: .verificationViolation)
|
||||
VerificationBadge(verificationState: .verified)
|
||||
}
|
||||
.previewLayout(.sizeThatFits)
|
||||
}
|
||||
}
|
||||
@@ -12,17 +12,25 @@ import SwiftUI
|
||||
struct RoomHeaderView: View {
|
||||
let roomName: String
|
||||
let roomAvatar: RoomAvatar
|
||||
var dmRecipientVerificationState: UserIdentityVerificationState?
|
||||
|
||||
let mediaProvider: MediaProviderProtocol?
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 12) {
|
||||
HStack(spacing: 8) {
|
||||
avatarImage
|
||||
.accessibilityHidden(true)
|
||||
|
||||
HStack(spacing: 4) {
|
||||
Text(roomName)
|
||||
.lineLimit(1)
|
||||
.font(.compound.bodyLGSemibold)
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomScreen.name)
|
||||
|
||||
if let dmRecipientVerificationState {
|
||||
VerificationBadge(verificationState: dmRecipientVerificationState)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Take up as much space as possible, with a leading alignment for use in the principal toolbar position.
|
||||
.frame(idealWidth: .greatestFiniteMagnitude, maxWidth: .infinity, alignment: .leading)
|
||||
@@ -38,20 +46,23 @@ struct RoomHeaderView: View {
|
||||
|
||||
struct RoomHeaderView_Previews: PreviewProvider, TestablePreview {
|
||||
static var previews: some View {
|
||||
RoomHeaderView(roomName: "Some Room name",
|
||||
roomAvatar: .room(id: "1",
|
||||
name: "Some Room Name",
|
||||
avatarURL: .mockMXCAvatar),
|
||||
mediaProvider: MediaProviderMock(configuration: .init()))
|
||||
VStack(spacing: 8) {
|
||||
makeHeader(avatarURL: nil, verificationState: .notVerified)
|
||||
makeHeader(avatarURL: .mockMXCAvatar, verificationState: .notVerified)
|
||||
makeHeader(avatarURL: .mockMXCAvatar, verificationState: .verified)
|
||||
makeHeader(avatarURL: .mockMXCAvatar, verificationState: .verificationViolation)
|
||||
}
|
||||
.previewLayout(.sizeThatFits)
|
||||
.padding()
|
||||
}
|
||||
|
||||
static func makeHeader(avatarURL: URL?,
|
||||
verificationState: UserIdentityVerificationState) -> some View {
|
||||
RoomHeaderView(roomName: "Some Room name",
|
||||
roomAvatar: .room(id: "1",
|
||||
name: "Some Room Name",
|
||||
avatarURL: nil),
|
||||
avatarURL: avatarURL),
|
||||
dmRecipientVerificationState: verificationState,
|
||||
mediaProvider: MediaProviderMock(configuration: .init()))
|
||||
.previewLayout(.sizeThatFits)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,10 @@ struct RoomDetailsScreenViewState: BindableState {
|
||||
|
||||
var topic: AttributedString?
|
||||
var topicSummary: AttributedString?
|
||||
|
||||
var joinedMembersCount: Int
|
||||
var hasMemberIdentityVerificationStateViolations = false
|
||||
|
||||
var isProcessingIgnoreRequest = false
|
||||
var canInviteUsers = false
|
||||
var canEditRoomName = false
|
||||
|
||||
@@ -200,6 +200,12 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
||||
.throttle(for: .milliseconds(100), scheduler: DispatchQueue.main, latest: true)
|
||||
.weakAssign(to: \.state.knockRequestsCount, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
roomProxy.membersPublisher.combineLatest(roomProxy.identityStatusChangesPublisher)
|
||||
.sink { _ in
|
||||
Task { await self.updateMemberIdentityVerificationStates() }
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
private func updateRoomInfo(_ roomInfo: RoomInfoProxy) {
|
||||
@@ -239,6 +245,24 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
||||
await roomProxy.updateMembers()
|
||||
}
|
||||
|
||||
private func updateMemberIdentityVerificationStates() async {
|
||||
guard roomProxy.isEncrypted else {
|
||||
// We don't care about identity statuses on non-encrypted rooms
|
||||
return
|
||||
}
|
||||
|
||||
for member in roomProxy.membersPublisher.value {
|
||||
if case let .success(identity) = await clientProxy.userIdentity(for: member.userID) {
|
||||
if identity?.verificationState == .verificationViolation {
|
||||
state.hasMemberIdentityVerificationStateViolations = true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.hasMemberIdentityVerificationStateViolations = false
|
||||
}
|
||||
|
||||
private func updatePowerLevelPermissions() async {
|
||||
state.canEditRoomName = await (try? roomProxy.canUser(userID: roomProxy.ownUserID, sendStateEvent: .roomName).get()) == true
|
||||
state.canEditRoomTopic = await (try? roomProxy.canUser(userID: roomProxy.ownUserID, sendStateEvent: .roomTopic).get()) == true
|
||||
|
||||
@@ -198,13 +198,22 @@ struct RoomDetailsScreen: View {
|
||||
|
||||
private var peopleSection: some View {
|
||||
Section {
|
||||
ListRow(label: .default(title: L10n.commonPeople,
|
||||
icon: \.user),
|
||||
if context.viewState.hasMemberIdentityVerificationStateViolations {
|
||||
ListRow(label: .default(title: L10n.commonPeople, icon: \.user),
|
||||
details: .icon(CompoundIcon(\.infoSolid).foregroundStyle(.compound.iconCriticalPrimary)),
|
||||
kind: .navigationLink {
|
||||
context.send(viewAction: .processTapPeople)
|
||||
})
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.people)
|
||||
|
||||
} else {
|
||||
ListRow(label: .default(title: L10n.commonPeople, icon: \.user),
|
||||
details: .title(String(context.viewState.joinedMembersCount)),
|
||||
kind: .navigationLink {
|
||||
context.send(viewAction: .processTapPeople)
|
||||
})
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.people)
|
||||
}
|
||||
|
||||
if context.viewState.canSeeKnockingRequests {
|
||||
ListRow(label: .default(title: L10n.screenRoomDetailsRequestsToJoinTitle,
|
||||
|
||||
+9
-4
@@ -17,7 +17,7 @@ enum RoomMemberDetailsScreenViewModelAction {
|
||||
struct RoomMemberDetailsScreenViewState: BindableState {
|
||||
let userID: String
|
||||
var memberDetails: RoomMemberDetails?
|
||||
var isVerified: Bool?
|
||||
var verificationState: UserIdentityVerificationState?
|
||||
var isOwnMemberDetails = false
|
||||
var isProcessingIgnoreRequest = false
|
||||
var dmRoomID: String?
|
||||
@@ -25,11 +25,15 @@ struct RoomMemberDetailsScreenViewState: BindableState {
|
||||
var bindings: RoomMemberDetailsScreenViewStateBindings
|
||||
|
||||
var showVerifiedBadge: Bool {
|
||||
isVerified == true // We purposely show the badge on your own account for consistency with Web.
|
||||
verificationState == .verified // We purposely show the badge on your own account for consistency with Web.
|
||||
}
|
||||
|
||||
var showVerificationSection: Bool {
|
||||
isVerified == false && !isOwnMemberDetails
|
||||
var showVerifyIdentitySection: Bool {
|
||||
verificationState == .notVerified && !isOwnMemberDetails
|
||||
}
|
||||
|
||||
var showWithdrawVerificationSection: Bool {
|
||||
verificationState == .verificationViolation && !isOwnMemberDetails
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +94,7 @@ enum RoomMemberDetailsScreenViewAction {
|
||||
case createDirectChat
|
||||
case startCall(roomID: String)
|
||||
case verifyUser
|
||||
case withdrawVerification
|
||||
}
|
||||
|
||||
enum RoomMemberDetailsScreenAlertType: Hashable {
|
||||
|
||||
+15
-6
@@ -42,10 +42,20 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro
|
||||
super.init(initialViewState: initialViewState, mediaProvider: mediaProvider)
|
||||
|
||||
showMemberLoadingIndicator()
|
||||
|
||||
Task {
|
||||
await loadMember()
|
||||
hideMemberLoadingIndicator()
|
||||
}
|
||||
|
||||
roomProxy.identityStatusChangesPublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { changes in
|
||||
if changes.map(\.userId).contains(userID) {
|
||||
Task { await self.loadMember() }
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
@@ -77,16 +87,15 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro
|
||||
actionsSubject.send(.startCall(roomID: roomID))
|
||||
case .verifyUser:
|
||||
actionsSubject.send(.verifyUser(userID: state.userID))
|
||||
case .withdrawVerification:
|
||||
Task { await clientProxy.withdrawUserIdentityVerification(state.userID) }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func loadMember() async {
|
||||
async let memberResult = roomProxy.getMember(userID: state.userID)
|
||||
async let identityResult = clientProxy.userIdentity(for: state.userID)
|
||||
|
||||
switch await memberResult {
|
||||
switch await roomProxy.getMember(userID: state.userID) {
|
||||
case .success(let member):
|
||||
roomMemberProxy = member
|
||||
state.memberDetails = RoomMemberDetails(withProxy: member)
|
||||
@@ -105,8 +114,8 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro
|
||||
actionsSubject.send(.openUserProfile)
|
||||
}
|
||||
|
||||
if case let .success(.some(identity)) = await identityResult {
|
||||
state.isVerified = identity.isVerified()
|
||||
if case let .success(.some(identity)) = await clientProxy.userIdentity(for: state.userID) {
|
||||
state.verificationState = identity.verificationState
|
||||
} else {
|
||||
MXLog.error("Failed to find the member's identity.")
|
||||
}
|
||||
|
||||
+50
-9
@@ -15,7 +15,9 @@ struct RoomMemberDetailsScreen: View {
|
||||
Form {
|
||||
headerSection
|
||||
|
||||
if context.viewState.showVerifyIdentitySection {
|
||||
verificationSection
|
||||
}
|
||||
|
||||
if context.viewState.memberDetails != nil, !context.viewState.isOwnMemberDetails {
|
||||
blockUserSection
|
||||
@@ -46,8 +48,15 @@ struct RoomMemberDetailsScreen: View {
|
||||
mediaProvider: context.mediaProvider) { url in
|
||||
context.send(viewAction: .displayAvatar(url))
|
||||
} footer: {
|
||||
VStack(spacing: 24) {
|
||||
if context.viewState.showWithdrawVerificationSection {
|
||||
withdrawVerificationSection
|
||||
}
|
||||
|
||||
otherUserFooter
|
||||
}
|
||||
.padding(.top, 24)
|
||||
}
|
||||
} else {
|
||||
AvatarHeaderView(user: UserProfileProxy(userID: context.viewState.userID),
|
||||
isVerified: context.viewState.showVerifiedBadge,
|
||||
@@ -56,6 +65,26 @@ struct RoomMemberDetailsScreen: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var withdrawVerificationSection: some View {
|
||||
VStack(spacing: 16) {
|
||||
if let memberDetails = context.viewState.memberDetails {
|
||||
Text(L10n.cryptoIdentityChangeProfilePinViolation(memberDetails.name ?? memberDetails.id))
|
||||
.foregroundStyle(.compound.textCriticalPrimary)
|
||||
.font(.compound.bodyMDSemibold)
|
||||
} else {
|
||||
Text(L10n.cryptoIdentityChangeProfilePinViolation(context.viewState.userID))
|
||||
.foregroundStyle(.compound.textCriticalPrimary)
|
||||
.font(.compound.bodyMDSemibold)
|
||||
}
|
||||
|
||||
Button(L10n.cryptoIdentityChangeWithdrawVerificationAction) {
|
||||
context.send(viewAction: .withdrawVerification)
|
||||
}
|
||||
.buttonStyle(.compound(.secondary, size: .medium))
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
|
||||
private var otherUserFooter: some View {
|
||||
HStack(spacing: 8) {
|
||||
if context.viewState.memberDetails != nil, !context.viewState.isOwnMemberDetails {
|
||||
@@ -84,20 +113,15 @@ struct RoomMemberDetailsScreen: View {
|
||||
.buttonStyle(FormActionButtonStyle(title: L10n.actionShare))
|
||||
}
|
||||
}
|
||||
.padding(.top, 32)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var verificationSection: some View {
|
||||
if context.viewState.showVerificationSection {
|
||||
Section {
|
||||
ListRow(label: .default(title: L10n.commonVerifyUser, icon: \.lock),
|
||||
kind: .button {
|
||||
ListRow(label: .default(title: L10n.commonVerifyUser, icon: \.lock), kind: .button {
|
||||
context.send(viewAction: .verifyUser)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var blockUserSection: some View {
|
||||
@@ -138,6 +162,7 @@ struct RoomMemberDetailsScreen: View {
|
||||
|
||||
struct RoomMemberDetailsScreen_Previews: PreviewProvider, TestablePreview {
|
||||
static let verifiedUserViewModel = makeViewModel(member: .mockDan)
|
||||
static let verificationViolationUserViewModel = makeViewModel(member: .mockBob)
|
||||
static let otherUserViewModel = makeViewModel(member: .mockAlice)
|
||||
static let accountOwnerViewModel = makeViewModel(member: .mockMe)
|
||||
static let ignoredUserViewModel = makeViewModel(member: .mockIgnored)
|
||||
@@ -145,10 +170,16 @@ struct RoomMemberDetailsScreen_Previews: PreviewProvider, TestablePreview {
|
||||
static var previews: some View {
|
||||
RoomMemberDetailsScreen(context: verifiedUserViewModel.context)
|
||||
.snapshotPreferences(expect: verifiedUserViewModel.context.$viewState.map { state in
|
||||
state.isVerified == true
|
||||
state.verificationState == .verified
|
||||
})
|
||||
.previewDisplayName("Verified User")
|
||||
|
||||
RoomMemberDetailsScreen(context: verificationViolationUserViewModel.context)
|
||||
.snapshotPreferences(expect: verificationViolationUserViewModel.context.$viewState.map { state in
|
||||
state.verificationState == .verificationViolation
|
||||
})
|
||||
.previewDisplayName("Verification Violation User")
|
||||
|
||||
RoomMemberDetailsScreen(context: otherUserViewModel.context)
|
||||
.snapshotPreferences(expect: otherUserViewModel.context.$viewState.map { state in
|
||||
state.memberDetails?.role == .user && state.dmRoomID != nil
|
||||
@@ -171,12 +202,22 @@ struct RoomMemberDetailsScreen_Previews: PreviewProvider, TestablePreview {
|
||||
static func makeViewModel(member: RoomMemberProxyMock) -> RoomMemberDetailsScreenViewModel {
|
||||
let roomProxyMock = JoinedRoomProxyMock(.init(name: ""))
|
||||
roomProxyMock.getMemberUserIDReturnValue = .success(member)
|
||||
|
||||
let clientProxyMock = ClientProxyMock(.init())
|
||||
|
||||
clientProxyMock.userIdentityForClosure = { userID in
|
||||
let isVerified = userID == RoomMemberProxyMock.mockDan.userID
|
||||
return .success(UserIdentitySDKMock(configuration: .init(isVerified: isVerified)))
|
||||
let identity = switch userID {
|
||||
case RoomMemberProxyMock.mockDan.userID:
|
||||
UserIdentityProxyMock(configuration: .init(verificationState: .verified))
|
||||
case RoomMemberProxyMock.mockBob.userID:
|
||||
UserIdentityProxyMock(configuration: .init(verificationState: .verificationViolation))
|
||||
default:
|
||||
UserIdentityProxyMock(configuration: .init())
|
||||
}
|
||||
|
||||
return .success(identity)
|
||||
}
|
||||
|
||||
// to avoid mock the call state for the account owner test case
|
||||
if member.userID != RoomMemberProxyMock.mockMe.userID {
|
||||
clientProxyMock.directRoomForUserIDReturnValue = .success("roomID")
|
||||
|
||||
+4
-2
@@ -9,8 +9,9 @@ import Combine
|
||||
import SwiftUI
|
||||
|
||||
struct RoomMembersListScreenCoordinatorParameters {
|
||||
let mediaProvider: MediaProviderProtocol
|
||||
let clientProxy: ClientProxyProtocol
|
||||
let roomProxy: JoinedRoomProxyProtocol
|
||||
let mediaProvider: MediaProviderProtocol
|
||||
let userIndicatorController: UserIndicatorControllerProtocol
|
||||
let analytics: AnalyticsService
|
||||
}
|
||||
@@ -31,7 +32,8 @@ final class RoomMembersListScreenCoordinator: CoordinatorProtocol {
|
||||
}
|
||||
|
||||
init(parameters: RoomMembersListScreenCoordinatorParameters) {
|
||||
viewModel = RoomMembersListScreenViewModel(roomProxy: parameters.roomProxy,
|
||||
viewModel = RoomMembersListScreenViewModel(clientProxy: parameters.clientProxy,
|
||||
roomProxy: parameters.roomProxy,
|
||||
mediaProvider: parameters.mediaProvider,
|
||||
userIndicatorController: parameters.userIndicatorController,
|
||||
analytics: parameters.analytics)
|
||||
|
||||
@@ -29,10 +29,15 @@ enum RoomMembersListScreenMode {
|
||||
case banned
|
||||
}
|
||||
|
||||
struct RoomMemberListScreenEntry: Equatable {
|
||||
let member: RoomMemberDetails
|
||||
let verificationState: UserIdentityVerificationState
|
||||
}
|
||||
|
||||
struct RoomMembersListScreenViewState: BindableState {
|
||||
private var joinedMembers: [RoomMemberDetails]
|
||||
private var invitedMembers: [RoomMemberDetails]
|
||||
private var bannedMembers: [RoomMemberDetails]
|
||||
private var joinedMembers: [RoomMemberListScreenEntry]
|
||||
private var invitedMembers: [RoomMemberListScreenEntry]
|
||||
private var bannedMembers: [RoomMemberListScreenEntry]
|
||||
|
||||
let joinedMembersCount: Int
|
||||
var bannedMembersCount: Int { bannedMembers.count }
|
||||
@@ -44,9 +49,9 @@ struct RoomMembersListScreenViewState: BindableState {
|
||||
var bindings: RoomMembersListScreenViewStateBindings
|
||||
|
||||
init(joinedMembersCount: Int,
|
||||
joinedMembers: [RoomMemberDetails] = [],
|
||||
invitedMembers: [RoomMemberDetails] = [],
|
||||
bannedMembers: [RoomMemberDetails] = [],
|
||||
joinedMembers: [RoomMemberListScreenEntry] = [],
|
||||
invitedMembers: [RoomMemberListScreenEntry] = [],
|
||||
bannedMembers: [RoomMemberListScreenEntry] = [],
|
||||
bindings: RoomMembersListScreenViewStateBindings) {
|
||||
self.joinedMembersCount = joinedMembersCount
|
||||
self.joinedMembers = joinedMembers
|
||||
@@ -55,19 +60,19 @@ struct RoomMembersListScreenViewState: BindableState {
|
||||
self.bindings = bindings
|
||||
}
|
||||
|
||||
var visibleJoinedMembers: [RoomMemberDetails] {
|
||||
var visibleJoinedMembers: [RoomMemberListScreenEntry] {
|
||||
joinedMembers
|
||||
.filter { $0.matches(searchQuery: bindings.searchQuery) }
|
||||
.filter { $0.member.matches(searchQuery: bindings.searchQuery) }
|
||||
}
|
||||
|
||||
var visibleInvitedMembers: [RoomMemberDetails] {
|
||||
var visibleInvitedMembers: [RoomMemberListScreenEntry] {
|
||||
invitedMembers
|
||||
.filter { $0.matches(searchQuery: bindings.searchQuery) }
|
||||
.filter { $0.member.matches(searchQuery: bindings.searchQuery) }
|
||||
}
|
||||
|
||||
var visibleBannedMembers: [RoomMemberDetails] {
|
||||
var visibleBannedMembers: [RoomMemberListScreenEntry] {
|
||||
bannedMembers
|
||||
.filter { $0.matches(searchQuery: bindings.searchQuery) }
|
||||
.filter { $0.member.matches(searchQuery: bindings.searchQuery) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+37
-24
@@ -11,6 +11,7 @@ import SwiftUI
|
||||
typealias RoomMembersListScreenViewModelType = StateStoreViewModel<RoomMembersListScreenViewState, RoomMembersListScreenViewAction>
|
||||
|
||||
class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMembersListScreenViewModelProtocol {
|
||||
private let clientProxy: ClientProxyProtocol
|
||||
private let roomProxy: JoinedRoomProxyProtocol
|
||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||
private let analytics: AnalyticsService
|
||||
@@ -24,10 +25,12 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
|
||||
}
|
||||
|
||||
init(initialMode: RoomMembersListScreenMode = .members,
|
||||
clientProxy: ClientProxyProtocol,
|
||||
roomProxy: JoinedRoomProxyProtocol,
|
||||
mediaProvider: MediaProviderProtocol,
|
||||
userIndicatorController: UserIndicatorControllerProtocol,
|
||||
analytics: AnalyticsService) {
|
||||
self.clientProxy = clientProxy
|
||||
self.roomProxy = roomProxy
|
||||
self.userIndicatorController = userIndicatorController
|
||||
self.analytics = analytics
|
||||
@@ -59,22 +62,23 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
|
||||
}
|
||||
|
||||
func stop() {
|
||||
hideLoader()
|
||||
hideLoadingIndicator(Self.setupMembersLoadingIndicatorIdentifier)
|
||||
hideLoadingIndicator(Self.updateStateLoadingIndicatorIdentifier)
|
||||
}
|
||||
|
||||
// MARK: - Members
|
||||
|
||||
private func setupMembers() {
|
||||
Task {
|
||||
showLoader()
|
||||
showLoadingIndicator(Self.setupMembersLoadingIndicatorIdentifier)
|
||||
await roomProxy.updateMembers()
|
||||
hideLoader()
|
||||
hideLoadingIndicator(Self.setupMembersLoadingIndicatorIdentifier)
|
||||
}
|
||||
|
||||
roomProxy.membersPublisher
|
||||
.filter { !$0.isEmpty }
|
||||
roomProxy.membersPublisher.combineLatest(roomProxy.identityStatusChangesPublisher)
|
||||
.filter { !$0.0.isEmpty }
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] members in
|
||||
.sink { [weak self] members, _ in
|
||||
self?.updateState(members: members)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
@@ -87,11 +91,12 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
|
||||
|
||||
private func updateState(members: [RoomMemberProxyProtocol]) {
|
||||
Task {
|
||||
showLoader()
|
||||
showLoadingIndicator(Self.updateStateLoadingIndicatorIdentifier)
|
||||
|
||||
let members = members.sorted()
|
||||
let roomMembersDetails = await buildMembersDetails(members: members)
|
||||
self.members = members
|
||||
|
||||
self.state = .init(joinedMembersCount: roomProxy.infoPublisher.value.joinedMembersCount,
|
||||
joinedMembers: roomMembersDetails.joinedMembers,
|
||||
invitedMembers: roomMembersDetails.invitedMembers,
|
||||
@@ -102,25 +107,32 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
|
||||
self.state.canKickUsers = await (try? roomProxy.canUserKick(userID: roomProxy.ownUserID).get()) == true
|
||||
self.state.canBanUsers = await (try? roomProxy.canUserBan(userID: roomProxy.ownUserID).get()) == true
|
||||
|
||||
hideLoader()
|
||||
hideLoadingIndicator(Self.updateStateLoadingIndicatorIdentifier)
|
||||
}
|
||||
}
|
||||
|
||||
private func buildMembersDetails(members: [RoomMemberProxyProtocol]) async -> RoomMembersDetails {
|
||||
await Task.detached {
|
||||
await Task.detached { [clientProxy, roomProxy] in
|
||||
// accessing RoomMember's properties is very slow. We need to do it in a background thread.
|
||||
var invitedMembers: [RoomMemberDetails] = .init()
|
||||
var joinedMembers: [RoomMemberDetails] = .init()
|
||||
var bannedMembers: [RoomMemberDetails] = .init()
|
||||
var invitedMembers: [RoomMemberListScreenEntry] = .init()
|
||||
var joinedMembers: [RoomMemberListScreenEntry] = .init()
|
||||
var bannedMembers: [RoomMemberListScreenEntry] = .init()
|
||||
|
||||
for member in members {
|
||||
var verificationState: UserIdentityVerificationState = .notVerified
|
||||
if roomProxy.isEncrypted, // We don't care about identity statuses on non-encrypted rooms
|
||||
case let .success(userIdentity) = await clientProxy.userIdentity(for: member.userID),
|
||||
let userIdentity {
|
||||
verificationState = userIdentity.verificationState
|
||||
}
|
||||
|
||||
switch member.membership {
|
||||
case .invite:
|
||||
invitedMembers.append(.init(withProxy: member))
|
||||
invitedMembers.append(.init(member: .init(withProxy: member), verificationState: verificationState))
|
||||
case .join:
|
||||
joinedMembers.append(.init(withProxy: member))
|
||||
joinedMembers.append(.init(member: .init(withProxy: member), verificationState: verificationState))
|
||||
case .ban:
|
||||
bannedMembers.append(.init(withProxy: member))
|
||||
bannedMembers.append(.init(member: .init(withProxy: member), verificationState: verificationState))
|
||||
default:
|
||||
continue
|
||||
}
|
||||
@@ -128,7 +140,7 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
|
||||
|
||||
return .init(invitedMembers: invitedMembers,
|
||||
joinedMembers: joinedMembers,
|
||||
bannedMembers: bannedMembers.sorted { $0.id.localizedStandardCompare($1.id) == .orderedAscending }) // Re-sort ignoring display name.
|
||||
bannedMembers: bannedMembers.sorted { $0.member.id.localizedStandardCompare($1.member.id) == .orderedAscending }) // Re-sort ignoring display name.
|
||||
}
|
||||
.value
|
||||
}
|
||||
@@ -215,18 +227,19 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
|
||||
|
||||
// MARK: - Indicators
|
||||
|
||||
private let userIndicatorID = UUID().uuidString
|
||||
private static let setupMembersLoadingIndicatorIdentifier = "\(RoomMembersListScreenViewModel.self)-SetupMembers"
|
||||
private static let updateStateLoadingIndicatorIdentifier = "\(RoomMembersListScreenViewModel.self)-UpdateState"
|
||||
|
||||
private func showLoader() {
|
||||
userIndicatorController.submitIndicator(UserIndicator(id: userIndicatorID,
|
||||
private func showLoadingIndicator(_ identifier: String) {
|
||||
userIndicatorController.submitIndicator(UserIndicator(id: identifier,
|
||||
type: .modal(progress: .indeterminate, interactiveDismissDisabled: false, allowsInteraction: true),
|
||||
title: L10n.commonLoading,
|
||||
persistent: true),
|
||||
delay: .milliseconds(200))
|
||||
}
|
||||
|
||||
private func hideLoader() {
|
||||
userIndicatorController.retractIndicatorWithId(userIndicatorID)
|
||||
private func hideLoadingIndicator(_ identifier: String) {
|
||||
userIndicatorController.retractIndicatorWithId(identifier)
|
||||
}
|
||||
|
||||
private func showManageMemberIndicator(title: String) {
|
||||
@@ -247,7 +260,7 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
|
||||
}
|
||||
|
||||
private struct RoomMembersDetails {
|
||||
var invitedMembers: [RoomMemberDetails]
|
||||
var joinedMembers: [RoomMemberDetails]
|
||||
var bannedMembers: [RoomMemberDetails]
|
||||
var invitedMembers: [RoomMemberListScreenEntry]
|
||||
var joinedMembers: [RoomMemberListScreenEntry]
|
||||
var bannedMembers: [RoomMemberListScreenEntry]
|
||||
}
|
||||
|
||||
+1
@@ -97,6 +97,7 @@ struct RoomMembersListManageMemberSheetLive_Previews: PreviewProvider {
|
||||
private extension RoomMembersListScreenViewModel {
|
||||
static var mock: RoomMembersListScreenViewModel {
|
||||
RoomMembersListScreenViewModel(initialMode: .members,
|
||||
clientProxy: ClientProxyMock(.init()),
|
||||
roomProxy: JoinedRoomProxyMock(.init(members: .allMembersAsAdmin)),
|
||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
|
||||
@@ -59,23 +59,23 @@ struct RoomMembersListScreen: View {
|
||||
|
||||
var roomMembers: some View {
|
||||
LazyVStack(alignment: .leading, spacing: 12) {
|
||||
membersSection(data: context.viewState.visibleInvitedMembers, sectionTitle: L10n.screenRoomMemberListPendingHeaderTitle)
|
||||
membersSection(data: context.viewState.visibleJoinedMembers, sectionTitle: L10n.screenRoomMemberListHeaderTitle(Int(context.viewState.joinedMembersCount)))
|
||||
membersSection(entries: context.viewState.visibleInvitedMembers, sectionTitle: L10n.screenRoomMemberListPendingHeaderTitle)
|
||||
membersSection(entries: context.viewState.visibleJoinedMembers, sectionTitle: L10n.screenRoomMemberListHeaderTitle(Int(context.viewState.joinedMembersCount)))
|
||||
}
|
||||
}
|
||||
|
||||
var bannedUsers: some View {
|
||||
LazyVStack(alignment: .leading, spacing: 12) {
|
||||
membersSection(data: context.viewState.visibleBannedMembers)
|
||||
membersSection(entries: context.viewState.visibleBannedMembers)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func membersSection(data: [RoomMemberDetails], sectionTitle: String? = nil) -> some View {
|
||||
if !data.isEmpty {
|
||||
private func membersSection(entries: [RoomMemberListScreenEntry], sectionTitle: String? = nil) -> some View {
|
||||
if !entries.isEmpty {
|
||||
Section {
|
||||
ForEach(data, id: \.id) { member in
|
||||
RoomMembersListScreenMemberCell(member: member, context: context)
|
||||
ForEach(entries, id: \.member.id) { entry in
|
||||
RoomMembersListScreenMemberCell(listEntry: entry, context: context)
|
||||
}
|
||||
} header: {
|
||||
if let sectionTitle {
|
||||
@@ -179,7 +179,22 @@ struct RoomMembersListScreen_Previews: PreviewProvider, TestablePreview {
|
||||
members.append(.mockInvited)
|
||||
}
|
||||
|
||||
let clientProxyMock = ClientProxyMock(.init())
|
||||
clientProxyMock.userIdentityForClosure = { userID in
|
||||
let identity = switch userID {
|
||||
case RoomMemberProxyMock.mockAlice.userID:
|
||||
UserIdentityProxyMock(configuration: .init(verificationState: .verified))
|
||||
case RoomMemberProxyMock.mockBob.userID:
|
||||
UserIdentityProxyMock(configuration: .init(verificationState: .verificationViolation))
|
||||
default:
|
||||
UserIdentityProxyMock(configuration: .init())
|
||||
}
|
||||
|
||||
return .success(identity)
|
||||
}
|
||||
|
||||
return RoomMembersListScreenViewModel(initialMode: initialMode,
|
||||
clientProxy: clientProxyMock,
|
||||
roomProxy: JoinedRoomProxyMock(.init(name: "Some room",
|
||||
members: members,
|
||||
ownUserID: ownUserID,
|
||||
|
||||
+45
-26
@@ -8,22 +8,22 @@
|
||||
import SwiftUI
|
||||
|
||||
struct RoomMembersListScreenMemberCell: View {
|
||||
let member: RoomMemberDetails
|
||||
let listEntry: RoomMemberListScreenEntry
|
||||
let context: RoomMembersListScreenViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
context.send(viewAction: .selectMember(member))
|
||||
context.send(viewAction: .selectMember(listEntry.member))
|
||||
} label: {
|
||||
HStack(spacing: 8) {
|
||||
LoadableAvatarImage(url: avatarURL,
|
||||
name: avatarName,
|
||||
contentID: member.id,
|
||||
contentID: listEntry.member.id,
|
||||
avatarSize: .user(on: .roomDetails),
|
||||
mediaProvider: context.mediaProvider)
|
||||
.accessibilityHidden(true)
|
||||
|
||||
HStack(alignment: .firstTextBaseline, spacing: 4) {
|
||||
HStack(alignment: .center, spacing: 4) {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text(title)
|
||||
.font(.compound.bodyMDSemibold)
|
||||
@@ -39,6 +39,8 @@ struct RoomMembersListScreenMemberCell: View {
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
VerificationBadge(verificationState: listEntry.verificationState)
|
||||
|
||||
if let role {
|
||||
Text(role)
|
||||
.font(.compound.bodyXS)
|
||||
@@ -52,7 +54,7 @@ struct RoomMembersListScreenMemberCell: View {
|
||||
}
|
||||
|
||||
var role: String? {
|
||||
switch member.role {
|
||||
switch listEntry.member.role {
|
||||
case .administrator:
|
||||
L10n.screenRoomMemberListRoleAdministrator
|
||||
case .moderator:
|
||||
@@ -65,55 +67,72 @@ struct RoomMembersListScreenMemberCell: View {
|
||||
// Computed properties to hide the user's profile when banned.
|
||||
|
||||
var title: String {
|
||||
guard !member.isBanned else { return member.id }
|
||||
return member.name ?? member.id
|
||||
guard !listEntry.member.isBanned else { return listEntry.member.id }
|
||||
return listEntry.member.name ?? listEntry.member.id
|
||||
}
|
||||
|
||||
var subtitle: String? {
|
||||
member.isBanned ? nil : member.id
|
||||
listEntry.member.isBanned ? nil : listEntry.member.id
|
||||
}
|
||||
|
||||
var avatarName: String? {
|
||||
member.isBanned ? nil : member.name
|
||||
listEntry.member.isBanned ? nil : listEntry.member.name
|
||||
}
|
||||
|
||||
var avatarURL: URL? {
|
||||
member.isBanned ? nil : member.avatarURL
|
||||
listEntry.member.isBanned ? nil : listEntry.member.avatarURL
|
||||
}
|
||||
}
|
||||
|
||||
struct RoomMembersListMemberCell_Previews: PreviewProvider, TestablePreview {
|
||||
static let members: [RoomMemberProxyMock] = [
|
||||
.mockAlice,
|
||||
.mockAdmin,
|
||||
.mockModerator,
|
||||
.init(with: .init(userID: "@nodisplayname:matrix.org", membership: .join)),
|
||||
.init(with: .init(userID: "@avatar:matrix.org", displayName: "Avatar", avatarURL: .mockMXCUserAvatar, membership: .join))
|
||||
static let members: [RoomMemberListScreenEntry] = [
|
||||
.init(member: .init(withProxy: RoomMemberProxyMock.mockAlice),
|
||||
verificationState: .notVerified),
|
||||
.init(member: .init(withProxy: RoomMemberProxyMock.mockAdmin),
|
||||
verificationState: .verified),
|
||||
.init(member: .init(withProxy: RoomMemberProxyMock.mockModerator),
|
||||
verificationState: .verificationViolation),
|
||||
.init(member: .init(withProxy: RoomMemberProxyMock(with: .init(userID: "@nodisplayname:matrix.org",
|
||||
membership: .join))),
|
||||
verificationState: .notVerified),
|
||||
.init(member: .init(withProxy: RoomMemberProxyMock(with: .init(userID: "@avatar:matrix.org",
|
||||
displayName: "Avatar",
|
||||
avatarURL: .mockMXCUserAvatar,
|
||||
membership: .join))),
|
||||
verificationState: .notVerified)
|
||||
]
|
||||
|
||||
static let bannedMembers: [RoomMemberProxyMock] = [
|
||||
.init(with: .init(userID: "@nodisplayname:matrix.org", membership: .ban)),
|
||||
.init(with: .init(userID: "@fake:matrix.org", displayName: "President", membership: .ban)),
|
||||
.init(with: .init(userID: "@badavatar:matrix.org", avatarURL: .mockMXCUserAvatar, membership: .ban))
|
||||
static let bannedMembers: [RoomMemberListScreenEntry] = [
|
||||
.init(member: .init(withProxy: RoomMemberProxyMock(with: .init(userID: "@nodisplayname:matrix.org",
|
||||
membership: .ban))),
|
||||
verificationState: .notVerified),
|
||||
.init(member: .init(withProxy: RoomMemberProxyMock(with: .init(userID: "@fake:matrix.org",
|
||||
displayName: "President",
|
||||
membership: .ban))),
|
||||
verificationState: .verified),
|
||||
.init(member: .init(withProxy: RoomMemberProxyMock(with: .init(userID: "@badavatar:matrix.org",
|
||||
avatarURL: .mockMXCUserAvatar,
|
||||
membership: .ban))),
|
||||
verificationState: .verificationViolation)
|
||||
]
|
||||
|
||||
static let viewModel = RoomMembersListScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Some room",
|
||||
members: members)),
|
||||
static let viewModel = RoomMembersListScreenViewModel(clientProxy: ClientProxyMock(.init()),
|
||||
roomProxy: JoinedRoomProxyMock(.init(name: "Some room", members: [])),
|
||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
analytics: ServiceLocator.shared.analytics)
|
||||
static var previews: some View {
|
||||
VStack(spacing: 12) {
|
||||
Section("Invited/Joined") {
|
||||
ForEach(members, id: \.userID) { member in
|
||||
RoomMembersListScreenMemberCell(member: .init(withProxy: member), context: viewModel.context)
|
||||
ForEach(members, id: \.member.id) { entry in
|
||||
RoomMembersListScreenMemberCell(listEntry: entry, context: viewModel.context)
|
||||
}
|
||||
}
|
||||
|
||||
// Banned members should have their profiles hidden and the avatar should use the first letter from their user ID.
|
||||
Section("Banned") {
|
||||
ForEach(bannedMembers, id: \.userID) { member in
|
||||
RoomMembersListScreenMemberCell(member: .init(withProxy: member), context: viewModel.context)
|
||||
ForEach(bannedMembers, id: \.member.id) { entry in
|
||||
RoomMembersListScreenMemberCell(listEntry: entry, context: viewModel.context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ enum RoomScreenViewAction {
|
||||
struct RoomScreenViewState: BindableState {
|
||||
var roomTitle = ""
|
||||
var roomAvatar: RoomAvatar
|
||||
var dmRecipientVerificationState: UserIdentityVerificationState?
|
||||
|
||||
var lastScrollDirection: ScrollDirection?
|
||||
// This is used to control the banner
|
||||
|
||||
@@ -77,6 +77,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
|
||||
Task {
|
||||
await handleRoomInfoUpdate(roomProxy.infoPublisher.value)
|
||||
|
||||
await updateVerificationBadge()
|
||||
}
|
||||
|
||||
setupSubscriptions(ongoingCallRoomIDPublisher: ongoingCallRoomIDPublisher)
|
||||
@@ -182,6 +184,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
}
|
||||
|
||||
await self?.processIdentityStatusChanges(changes)
|
||||
await self?.updateVerificationBadge()
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
@@ -262,6 +265,23 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
}
|
||||
}
|
||||
|
||||
private func updateVerificationBadge() async {
|
||||
guard roomProxy.isDirectOneToOneRoom,
|
||||
let dmRecipient = roomProxy.membersPublisher.value.first(where: { $0.userID != roomProxy.ownUserID }),
|
||||
case let .success(userIdentity) = await clientProxy.userIdentity(for: dmRecipient.userID) else {
|
||||
state.dmRecipientVerificationState = .notVerified
|
||||
return
|
||||
}
|
||||
|
||||
guard let userIdentity else {
|
||||
MXLog.failure("User identity should be known at this point")
|
||||
state.dmRecipientVerificationState = .notVerified
|
||||
return
|
||||
}
|
||||
|
||||
state.dmRecipientVerificationState = userIdentity.verificationState
|
||||
}
|
||||
|
||||
private func resolveIdentityPinningViolation(_ userID: String) async {
|
||||
defer {
|
||||
hideLoadingIndicator()
|
||||
|
||||
@@ -204,6 +204,7 @@ struct RoomScreen: View {
|
||||
ToolbarItem(placement: .principal) {
|
||||
RoomHeaderView(roomName: roomContext.viewState.roomTitle,
|
||||
roomAvatar: roomContext.viewState.roomAvatar,
|
||||
dmRecipientVerificationState: roomContext.viewState.dmRecipientVerificationState,
|
||||
mediaProvider: roomContext.mediaProvider)
|
||||
// Using a button stops it from getting truncated in the navigation bar
|
||||
.contentShape(.rect)
|
||||
|
||||
@@ -20,7 +20,6 @@ struct UserProfileScreenCoordinatorParameters {
|
||||
enum UserProfileScreenCoordinatorAction {
|
||||
case openDirectChat(roomID: String)
|
||||
case startCall(roomID: String)
|
||||
case verifyUser(userID: String)
|
||||
case dismiss
|
||||
}
|
||||
|
||||
@@ -52,8 +51,6 @@ final class UserProfileScreenCoordinator: CoordinatorProtocol {
|
||||
actionsSubject.send(.openDirectChat(roomID: roomID))
|
||||
case .startCall(let roomID):
|
||||
actionsSubject.send(.startCall(roomID: roomID))
|
||||
case .verifyUser(let userID):
|
||||
actionsSubject.send(.verifyUser(userID: userID))
|
||||
case .dismiss:
|
||||
actionsSubject.send(.dismiss)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import Foundation
|
||||
enum UserProfileScreenViewModelAction {
|
||||
case openDirectChat(roomID: String)
|
||||
case startCall(roomID: String)
|
||||
case verifyUser(userID: String)
|
||||
case dismiss
|
||||
}
|
||||
|
||||
@@ -29,10 +28,6 @@ struct UserProfileScreenViewState: BindableState {
|
||||
var showVerifiedBadge: Bool {
|
||||
isVerified == true // We purposely show the badge on your own account for consistency with Web.
|
||||
}
|
||||
|
||||
var showVerificationSection: Bool {
|
||||
isVerified == false && !isOwnUser
|
||||
}
|
||||
}
|
||||
|
||||
struct UserProfileScreenViewStateBindings {
|
||||
@@ -48,7 +43,6 @@ enum UserProfileScreenViewAction {
|
||||
case openDirectChat
|
||||
case createDirectChat
|
||||
case startCall(roomID: String)
|
||||
case verifyUser
|
||||
case dismiss
|
||||
}
|
||||
|
||||
|
||||
@@ -66,8 +66,6 @@ class UserProfileScreenViewModel: UserProfileScreenViewModelType, UserProfileScr
|
||||
Task { await createDirectChat() }
|
||||
case .startCall(let roomID):
|
||||
actionsSubject.send(.startCall(roomID: roomID))
|
||||
case .verifyUser:
|
||||
actionsSubject.send(.verifyUser(userID: state.userID))
|
||||
case .dismiss:
|
||||
actionsSubject.send(.dismiss)
|
||||
}
|
||||
@@ -95,7 +93,7 @@ class UserProfileScreenViewModel: UserProfileScreenViewModelType, UserProfileScr
|
||||
}
|
||||
|
||||
if case let .success(.some(identity)) = await identityResult {
|
||||
state.isVerified = identity.isVerified()
|
||||
state.isVerified = identity.verificationState == .verified
|
||||
} else {
|
||||
MXLog.error("Failed to find the user's identity.")
|
||||
}
|
||||
|
||||
@@ -14,8 +14,6 @@ struct UserProfileScreen: View {
|
||||
var body: some View {
|
||||
Form {
|
||||
headerSection
|
||||
|
||||
verificationSection
|
||||
}
|
||||
.compoundList()
|
||||
.navigationTitle(L10n.screenRoomMemberDetailsTitle)
|
||||
@@ -84,18 +82,6 @@ struct UserProfileScreen: View {
|
||||
.padding(.top, 32)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var verificationSection: some View {
|
||||
if context.viewState.showVerificationSection {
|
||||
Section {
|
||||
ListRow(label: .default(title: L10n.commonVerifyUser, icon: \.lock),
|
||||
kind: .button {
|
||||
context.send(viewAction: .verifyUser)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ToolbarContentBuilder
|
||||
private var toolbar: some ToolbarContent {
|
||||
if context.viewState.isPresentedModally {
|
||||
@@ -137,13 +123,22 @@ struct UserProfileScreen_Previews: PreviewProvider, TestablePreview {
|
||||
|
||||
static func makeViewModel(userID: String) -> UserProfileScreenViewModel {
|
||||
let clientProxyMock = ClientProxyMock(.init())
|
||||
|
||||
clientProxyMock.userIdentityForClosure = { userID in
|
||||
let isVerified = userID == RoomMemberProxyMock.mockDan.userID
|
||||
return .success(UserIdentitySDKMock(configuration: .init(isVerified: isVerified)))
|
||||
let identity = switch userID {
|
||||
case RoomMemberProxyMock.mockDan.userID:
|
||||
UserIdentityProxyMock(configuration: .init(verificationState: .verified))
|
||||
default:
|
||||
UserIdentityProxyMock(configuration: .init())
|
||||
}
|
||||
|
||||
return .success(identity)
|
||||
}
|
||||
|
||||
if userID != RoomMemberProxyMock.mockMe.userID {
|
||||
clientProxyMock.directRoomForUserIDReturnValue = .success("roomID")
|
||||
}
|
||||
|
||||
return UserProfileScreenViewModel(userID: userID,
|
||||
isPresentedModally: false,
|
||||
clientProxy: clientProxyMock,
|
||||
|
||||
@@ -1018,9 +1018,9 @@ class ClientProxy: ClientProxyProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func userIdentity(for userID: String) async -> Result<UserIdentity?, ClientProxyError> {
|
||||
func userIdentity(for userID: String) async -> Result<UserIdentityProxyProtocol?, ClientProxyError> {
|
||||
do {
|
||||
return try await .success(client.encryption().userIdentity(userId: userID))
|
||||
return try await .success(client.encryption().userIdentity(userId: userID).map(UserIdentityProxy.init))
|
||||
} catch {
|
||||
MXLog.error("Failed retrieving user identity: \(error)")
|
||||
return .failure(.sdkError(error))
|
||||
|
||||
@@ -191,5 +191,5 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
|
||||
func withdrawUserIdentityVerification(_ userID: String) async -> Result<Void, ClientProxyError>
|
||||
func resetIdentity() async -> Result<IdentityResetHandle?, ClientProxyError>
|
||||
|
||||
func userIdentity(for userID: String) async -> Result<UserIdentity?, ClientProxyError>
|
||||
func userIdentity(for userID: String) async -> Result<UserIdentityProxyProtocol?, ClientProxyError>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// Copyright 2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import MatrixRustSDK
|
||||
|
||||
class UserIdentityProxy: UserIdentityProxyProtocol {
|
||||
private let userIdentity: UserIdentity
|
||||
|
||||
init(userIdentity: UserIdentity) {
|
||||
self.userIdentity = userIdentity
|
||||
}
|
||||
|
||||
var verificationState: UserIdentityVerificationState {
|
||||
if userIdentity.hasVerificationViolation() {
|
||||
return .verificationViolation
|
||||
} else if userIdentity.isVerified() {
|
||||
return .verified
|
||||
}
|
||||
|
||||
return .notVerified
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Copyright 2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
enum UserIdentityVerificationState {
|
||||
case notVerified
|
||||
case verified
|
||||
case verificationViolation
|
||||
}
|
||||
|
||||
// sourcery: AutoMockable
|
||||
protocol UserIdentityProxyProtocol {
|
||||
var verificationState: UserIdentityVerificationState { get }
|
||||
}
|
||||
@@ -571,8 +571,9 @@ class MockScreen: Identifiable {
|
||||
case .roomMembersListScreenPendingInvites:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let members: [RoomMemberProxyMock] = [.mockInvitedAlice, .mockBob, .mockCharlie]
|
||||
let coordinator = RoomMembersListScreenCoordinator(parameters: .init(mediaProvider: MediaProviderMock(configuration: .init()),
|
||||
let coordinator = RoomMembersListScreenCoordinator(parameters: .init(clientProxy: ClientProxyMock(.init()),
|
||||
roomProxy: JoinedRoomProxyMock(.init(name: "test", members: members)),
|
||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
analytics: ServiceLocator.shared.analytics))
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
|
||||
@@ -1037,6 +1037,12 @@ extension PreviewTests {
|
||||
}
|
||||
}
|
||||
|
||||
func test_verificationBadge() async throws {
|
||||
for preview in VerificationBadge_Previews._allPreviews {
|
||||
try await assertSnapshots(matching: preview)
|
||||
}
|
||||
}
|
||||
|
||||
func test_videoMediaEventsTimelineView() async throws {
|
||||
for preview in VideoMediaEventsTimelineView_Previews._allPreviews {
|
||||
try await assertSnapshots(matching: preview)
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6ad1568981acd0a49c17795c4f9bf47c0270007639dd61effb68d708d49a8123
|
||||
size 111376
|
||||
oid sha256:153b4927280abdaa674e93f843c84b6dd1a23070c37556d57294d1504759ff2d
|
||||
size 111371
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c71906723f41774d71975cefc46b0ab8415e0fdebb63ec8768480a32935aeadb
|
||||
size 123103
|
||||
oid sha256:aa9c4ce4236f90ab89039f6353aa28091c2127b9d2acec0c808492f2b88750e3
|
||||
size 123098
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:19e3709844d8e70eebcd04eea5b56f2c32cb29c50626a3ae2834d839b8904efd
|
||||
size 68156
|
||||
oid sha256:8a9575ea8743524b675a174ae1e5b7bed6d85ac01f6b833daec9739997b0b2da
|
||||
size 68180
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ae6d231128edddb04ba4fffa915c2fcf1169ea28196cce8431da8d3eaa43d4e3
|
||||
size 82070
|
||||
oid sha256:48cc5116b97690c21daad083a15e9266068f9196a426b938020d3b57f8a4d8d2
|
||||
size 82093
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b8d4f33061bf7a6298eff4e3ffb63194b7b0eee56f2677c55e0db5c4db4e8a05
|
||||
size 19820
|
||||
oid sha256:5a3fcbc84d86ab21cfb58787fd83ab8e46e48e2fe35acaec61dff7f72fc0f938
|
||||
size 73712
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:94a649334de3e92c04c8c5fa24606ce4338b38867d3af71ba044ebc199bbf8d5
|
||||
size 11439
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b8d4f33061bf7a6298eff4e3ffb63194b7b0eee56f2677c55e0db5c4db4e8a05
|
||||
size 19820
|
||||
oid sha256:5a3fcbc84d86ab21cfb58787fd83ab8e46e48e2fe35acaec61dff7f72fc0f938
|
||||
size 73712
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:94a649334de3e92c04c8c5fa24606ce4338b38867d3af71ba044ebc199bbf8d5
|
||||
size 11439
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0a28fd17b8b3cbaa0d27c70019904244d5d29ee2fc83266512e8ae3b386e32cf
|
||||
size 15862
|
||||
oid sha256:1dfa74df7fac6d93e37ee9332a59b442c97a912261bc40b73c786090d613f259
|
||||
size 56152
|
||||
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:07945ef1e674a1744a26d59745956b94201a9a2b0fdb9a1c15933c32ce246e89
|
||||
size 7886
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0a28fd17b8b3cbaa0d27c70019904244d5d29ee2fc83266512e8ae3b386e32cf
|
||||
size 15862
|
||||
oid sha256:1dfa74df7fac6d93e37ee9332a59b442c97a912261bc40b73c786090d613f259
|
||||
size 56152
|
||||
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:07945ef1e674a1744a26d59745956b94201a9a2b0fdb9a1c15933c32ce246e89
|
||||
size 7886
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bb9a139e108b9cca1fceafa4fb2d4b2c0ea7a8b76a916a72cab682f1895a2cbb
|
||||
size 180293
|
||||
oid sha256:bc9057e946a55019caf464ab7437197b5bd0ab5e83076aba8a64bf65c309773b
|
||||
size 180336
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bc7992df6c2b7227c500f84531bdac9577db2cf067cb6aa9a81b81a8936771e4
|
||||
size 114445
|
||||
oid sha256:75cd91c40c97f338248fb5d5e76a291010115220c4094e27058206b5d7f1307b
|
||||
size 114637
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:eb8a03e056c72838db67ac97b012792f1d98985952dcaeffe80455363b729a5a
|
||||
size 114232
|
||||
oid sha256:a36746c71d1e8fc4379b20b17adeed7af104bd2bf2bc0f59fb9c9cd4b77f6bba
|
||||
size 114418
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bcf2cefb0e51250c908006f9380d69d2ecf46a71c4a86aa54e20995498c0eb47
|
||||
size 125040
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e764eb1952e4820a15affa00461fb1e2f4a3de0b7ff498560aab738e042c2c0b
|
||||
size 199433
|
||||
oid sha256:d4cca2b5da6c0a8bd3946a9d2a7ffb5b23a448c3e07eb0a5dea7bdab025c76c9
|
||||
size 199464
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f2f03c0e35ba417a0b55d2cade9bb8eeada26f0514497b411adc61675b25c03a
|
||||
size 180559
|
||||
oid sha256:48b686ff1f24cd842f4d8ae28c11c3a62113d2137e974c68f52e551bdb4514d8
|
||||
size 180604
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:26810d28eb8d22229c2fb6259d44182e8f459efc13d3da72af855c3bd217894c
|
||||
size 116662
|
||||
oid sha256:763fb73d2174b8c43a13eda6d833cfa59514069f3e5343d149f5b4db16db0ef2
|
||||
size 116882
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4deab2abb40430b6c26169284463fcb45ca30f93b703906e83a7a80b0dd44085
|
||||
size 116224
|
||||
oid sha256:add76379b3faa0915ddacb2f0fba8f0dafa629fcee802230b7b6fa04f84d87d1
|
||||
size 116404
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:622e29540af65ff83e1b0f4799431b3a44edba542d208f007f3027c612e3e043
|
||||
size 133323
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c1d159bceea6b6a367d6f6d1adc3c91f7c8ed5a89a12cc76d950108505e50834
|
||||
size 200973
|
||||
oid sha256:72f3469670b4e242ff96cfd50116c4413b50dc59f229257eeaddea84fc802bdc
|
||||
size 201004
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6bef0734ddc98e3d468375a8ed95de83880740c3cd9393506e49004f88a9c1b1
|
||||
size 121431
|
||||
oid sha256:56b877e9188f4dfaab208f93d34fe1dbaceed62e5122a87bbf5b5708d59ef789
|
||||
size 121429
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:99d9738fe1e134a9570a510ee4e0a7622bccc0edda2316d7e083cb6800f47567
|
||||
size 66185
|
||||
oid sha256:7cf3cda095d77c4336f766f2e8c42a8a6be21c460464080b33e4819fd955009e
|
||||
size 65826
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2890c691ada40cd89e8659f9e4397e5f2e0958cc3a7540d7f42f0d525fda4f46
|
||||
size 65267
|
||||
oid sha256:33df32f5444f672baadb0028384d11d2385c49eaf87734587efe686dfeb07ed4
|
||||
size 64908
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:03c2ebe4a22b3b8422e5f320f57a59ac2b8038c057327657878378458e5328d5
|
||||
size 74321
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f47c8be31c2dfb8109e021b872ddce7ce7d38a5015ada1a5332ee522b25f0382
|
||||
size 138264
|
||||
oid sha256:5030171dd1afe4cbf988eaca778cf9ed0500f7284bba7ac51fb3243e1f632899
|
||||
size 138283
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:22eb5c47c43b1c9fb7bc0f2806a90750374ad3825ab0d017d57bf0fd61d696b0
|
||||
size 122013
|
||||
oid sha256:6bdf3c45eb5a7ec4e44cf20b1a667f934b51c6316d05c85026749820544518df
|
||||
size 122011
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4a076ef632d402d507a09abaa38822a81a2a1a094aaee6de62e2aed796478266
|
||||
size 74470
|
||||
oid sha256:8a75782f44f8492435c9ebab3708644e955f14dca3ac35ab615a69984c7a71fa
|
||||
size 74437
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c796a02083a8469da92b6d764092caf259a0f6ee4ff52baf7851e5fcfa2be76d
|
||||
size 73660
|
||||
oid sha256:703f8fdb10d5d1081eb1bfddef855de60c9bb21ee90ff08420813fab23ee3ba6
|
||||
size 73622
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5b02fe793ca33790c1c6da7b1eabea1b828324c6faeacf242857d26b0b94eeb2
|
||||
size 93276
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:01b00054f275cb3ae4185449e7c5cbe375180260f0cde860ab290ed5dc6757b0
|
||||
size 146295
|
||||
oid sha256:2cc7c0a660c1ddf91e1f7336525dd18ce8a6a3ff1e1746e070119fe74b2a539c
|
||||
size 146322
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:02f47841f18c62a867c4ff1a23bf16edf17c81f5e97f249c2cff30a9d6c869f6
|
||||
size 169210
|
||||
oid sha256:150439a358f12324fd477216dffe3cbf9feccc704f019e00ad4f43e153bb97fa
|
||||
size 172233
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2e5bb0918b63578e8aa80fe8eaa088f4cff4eea4d4ab886b945362186e85ae5c
|
||||
size 172967
|
||||
oid sha256:9f2e7d68d38ac78087a52dfd0ef401fccda75d9a7112bc357ae5b034cecb48db
|
||||
size 175918
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a71ee5cc84935ecdfaa5fd164b46f01c1841ea4469282eaa424d729a65906148
|
||||
size 113290
|
||||
oid sha256:818255f45004091e759445a5541c9b04aa6c58926c7479e5e48f2da9ca9c0222
|
||||
size 115529
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3f67432fd158f5f91089243ea9919ade5e8b10a481625715a413d1e6ae580663
|
||||
size 117653
|
||||
oid sha256:803e62688fba1c2c652a59f52784e39da86f6a80dee0e0862925e75d80bf83cd
|
||||
size 119715
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b1157284b4d3bd61c1ed44c4f8c49cfd693cea4634f785d78a738e291d39d08e
|
||||
size 132965
|
||||
oid sha256:3e9df975598876636a317b5c6ab5a0f8d38c9afb7be4e25c4e6fad0c46bf5719
|
||||
size 134459
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:765e253eb1ab6eb5d9a77bc8b0a6c5d7e3ed064343ac055f9702b5fc810763aa
|
||||
size 136303
|
||||
oid sha256:feca48e2dcc6dc7f029b1b39716d3b1af94e1a12b6afc94cbf061fc68f2f3cac
|
||||
size 137742
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:02a3fb3f4dd20a12d2ff00ea73a6f88d4a30e96de1d6522b57a9c88ee1a7e05d
|
||||
size 125648
|
||||
oid sha256:c60d9ebc5781792b45e0237d8f69c524af59865f32e387c762f306744c411401
|
||||
size 127049
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5399631ce7f310512b85452f95230e717a1505787e612f3078f65f9ebd604857
|
||||
size 141900
|
||||
oid sha256:fb17504e82993df630ad2cb20854e3d2aa2f85baa3192f03800fb98419ce78b0
|
||||
size 143376
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d3be505a8d77dcef1b457e847e204efff0f7e76c81af095d3cfdd88f87a2e03c
|
||||
size 144426
|
||||
oid sha256:4d8d6fc490b3b7362bf326187c7526e00611bb585f3a2868bc0b1f98c6f9eaf4
|
||||
size 145856
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bf7e90926372085ad952e03139a9d3c0815b804cc2045145d878e00de65e4671
|
||||
size 133587
|
||||
oid sha256:ebef244d49b790601115f26146d52030fdca6632385a2c225bbd554d8e51d912
|
||||
size 135046
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3ebd4283903f8901a4fa2bcaca20b77a866d037581413f5908cc3a8d7dcfea03
|
||||
size 83790
|
||||
oid sha256:dc06908f037a0d2730d4a8b3b49742ddd24d72fc4ca5d0a14b1974385198ac78
|
||||
size 84815
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4429d4306851f0d7e6e07c216dfc1b7abb3b8380ce1d1ed53da6a54ae8220362
|
||||
size 86315
|
||||
oid sha256:9cb13eddbab510b9646fd2ee431e37208ef0d2f3b23204dce4fcf85032ba5891
|
||||
size 87183
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6a5fa320eb70ec2e1181dd063e76cd26ba9c2594c64b02058e77e9f536dfa99c
|
||||
size 77966
|
||||
oid sha256:521a418127816a652a7e4d17146c3eeb1d4aa613922f11b53ef63115ef90ed1e
|
||||
size 78984
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c598dafaa1584f2b8886183b1d9273c9b607322b493710725feeaec2f4d4f7e6
|
||||
size 93516
|
||||
oid sha256:1d804804fc7e19802cf4d3156c34e26e2be96085fc5fe9987403940541a3e08c
|
||||
size 94418
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d6ea9ff5daaa20a19ec8b822cba67eae0d8796b20a0121f33304b26c503e085d
|
||||
size 96436
|
||||
oid sha256:47335b9d2db5c87e10bbbe1b441d99c9283974ac5bf0ecd716a5bf172e17af29
|
||||
size 97152
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0951e99bb5bad9c5a5be23f56e7c302b38514c0760c47ef5a39d3ed982bb38f7
|
||||
size 86447
|
||||
oid sha256:39e5aafbe8a8629ec28da856475bd1e20f83060fa4b19d543d43f58c35d9bbca
|
||||
size 87331
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d19fdc59c123919099ce464a0bb5478edb8df05e23609cd5dae1b430bcd469d1
|
||||
size 295900
|
||||
oid sha256:02b4de01b6f3291309fdef1755bf2d1a4e989cecd777766394d512ef330fbfea
|
||||
size 295904
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:015366f6fe40da0753b014c0be10d6f46478d36e76fef8ac0d03608c44c8665d
|
||||
size 304454
|
||||
oid sha256:7be7c4f878b08ae3724587db110a3ea3ec1133f9731aca59924776ec59b54c66
|
||||
size 304445
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e5fe32d96cc563369893c88d566260a465e5ae7f372b10f75f1cd6b9fa93877e
|
||||
size 181804
|
||||
oid sha256:5236d9950c7d6179f4351b486bedb519e2afb29f65603d1ee3fea9b862936ba7
|
||||
size 181809
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6332e9688b6bc74fd053be710d9fc160016ff08fe08d755420871e5a79ad6c24
|
||||
size 180191
|
||||
oid sha256:3a9027ee84e95fa1b65e7287b311a109f829302563216d57b0f4a72fbb0fcedf
|
||||
size 180206
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d19fdc59c123919099ce464a0bb5478edb8df05e23609cd5dae1b430bcd469d1
|
||||
size 295900
|
||||
oid sha256:02b4de01b6f3291309fdef1755bf2d1a4e989cecd777766394d512ef330fbfea
|
||||
size 295904
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:015366f6fe40da0753b014c0be10d6f46478d36e76fef8ac0d03608c44c8665d
|
||||
size 304454
|
||||
oid sha256:7be7c4f878b08ae3724587db110a3ea3ec1133f9731aca59924776ec59b54c66
|
||||
size 304445
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e5fe32d96cc563369893c88d566260a465e5ae7f372b10f75f1cd6b9fa93877e
|
||||
size 181804
|
||||
oid sha256:5236d9950c7d6179f4351b486bedb519e2afb29f65603d1ee3fea9b862936ba7
|
||||
size 181809
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6332e9688b6bc74fd053be710d9fc160016ff08fe08d755420871e5a79ad6c24
|
||||
size 180191
|
||||
oid sha256:3a9027ee84e95fa1b65e7287b311a109f829302563216d57b0f4a72fbb0fcedf
|
||||
size 180206
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7c79cf23a403a1a8f7f870c5bc1b076d7db8fdc5306f8e2d570481b04f53243c
|
||||
size 108340
|
||||
oid sha256:7ec0cea60e7008080e384d60dbc8de8747cd55d8740b22292b286f04f9fe61db
|
||||
size 101556
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:12b13a72d49eed5e18f72dc9f9fe38e030e30cce68fa3e2ff0cf30f5fbe8967d
|
||||
size 110043
|
||||
oid sha256:f6883e72e65297302617486b88e8bca4e81e577aa40c888097f93099216f191f
|
||||
size 102538
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:cf92dab653565f100a445d2e8231e984761644436f2c9f565823f5c5730a0e28
|
||||
size 59553
|
||||
oid sha256:94fe46228c52208114afa3c511f09398921697390b27e83b1e4930eb17db5757
|
||||
size 53177
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:516372c5b8db4c73878cbce0be4015381446623317529c1a44a30fb114efd277
|
||||
size 66599
|
||||
oid sha256:62bf342ae32beb4efba3cff60e18da950cf406bbdcbd8b0e9bcca2e1906b1c06
|
||||
size 58597
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bc125492dfe1f025e9164856adc7c8c5c139c7b9f1e6ad6db977a2704e4649b3
|
||||
size 6588
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bc125492dfe1f025e9164856adc7c8c5c139c7b9f1e6ad6db977a2704e4649b3
|
||||
size 6588
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:95330c60b8d668574425379be7d0f41e8e17e11c76da95afaf5f89acfb68225e
|
||||
size 3755
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:95330c60b8d668574425379be7d0f41e8e17e11c76da95afaf5f89acfb68225e
|
||||
size 3755
|
||||
@@ -40,8 +40,18 @@ class RoomMembersListScreenViewModelTests: XCTestCase {
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
let sortedMembers: [RoomMemberProxyMock] = [.mockAdmin, .mockModerator, .mockAlice, .mockDan]
|
||||
XCTAssertEqual(viewModel.state.visibleJoinedMembers, sortedMembers.map(RoomMemberDetails.init))
|
||||
let sortedMembers: [RoomMemberListScreenEntry] = [
|
||||
.init(member: .init(withProxy: RoomMemberProxyMock.mockAdmin),
|
||||
verificationState: .notVerified),
|
||||
.init(member: .init(withProxy: RoomMemberProxyMock.mockModerator),
|
||||
verificationState: .notVerified),
|
||||
.init(member: .init(withProxy: RoomMemberProxyMock.mockAlice),
|
||||
verificationState: .notVerified),
|
||||
.init(member: .init(withProxy: RoomMemberProxyMock.mockDan),
|
||||
verificationState: .notVerified)
|
||||
]
|
||||
|
||||
XCTAssertEqual(viewModel.state.visibleJoinedMembers, sortedMembers)
|
||||
}
|
||||
|
||||
func testSearch() async throws {
|
||||
@@ -125,7 +135,7 @@ class RoomMembersListScreenViewModelTests: XCTestCase {
|
||||
|
||||
// When tapping on another user in the list.
|
||||
let memberDetailsAction = deferFulfillment(viewModel.actions) { $0.isSelectMember }
|
||||
guard let user = viewModel.state.visibleJoinedMembers.first(where: { $0.role == .user && $0.id != RoomMemberProxyMock.mockMe.userID }) else {
|
||||
guard let user = viewModel.state.visibleJoinedMembers.first(where: { $0.member.role == .user && $0.member.id != RoomMemberProxyMock.mockMe.userID })?.member else {
|
||||
XCTFail("Expected to find a regular user.")
|
||||
return
|
||||
}
|
||||
@@ -145,7 +155,7 @@ class RoomMembersListScreenViewModelTests: XCTestCase {
|
||||
|
||||
// When tapping on a user in the list.
|
||||
deferred = deferFulfillment(context.$viewState) { $0.bindings.memberToManage != nil }
|
||||
guard let user = viewModel.state.visibleJoinedMembers.first(where: { $0.role == .user && $0.id != RoomMemberProxyMock.mockMe.userID }) else {
|
||||
guard let user = viewModel.state.visibleJoinedMembers.first(where: { $0.member.role == .user && $0.member.id != RoomMemberProxyMock.mockMe.userID })?.member else {
|
||||
XCTFail("Expected to find a regular user.")
|
||||
return
|
||||
}
|
||||
@@ -166,7 +176,7 @@ class RoomMembersListScreenViewModelTests: XCTestCase {
|
||||
|
||||
// When tapping on a moderator in the list.
|
||||
deferred = deferFulfillment(context.$viewState) { $0.bindings.memberToManage != nil }
|
||||
guard let moderator = viewModel.state.visibleJoinedMembers.first(where: { $0.role == .moderator }) else {
|
||||
guard let moderator = viewModel.state.visibleJoinedMembers.first(where: { $0.member.role == .moderator })?.member else {
|
||||
XCTFail("Expected to find a moderator.")
|
||||
return
|
||||
}
|
||||
@@ -186,7 +196,7 @@ class RoomMembersListScreenViewModelTests: XCTestCase {
|
||||
|
||||
// When tapping on another administrator in the list.
|
||||
let memberDetailsAction = deferFulfillment(viewModel.actions) { $0.isSelectMember }
|
||||
guard let admin = viewModel.state.visibleJoinedMembers.first(where: { $0.role == .administrator && $0.id != RoomMemberProxyMock.mockMe.userID }) else {
|
||||
guard let admin = viewModel.state.visibleJoinedMembers.first(where: { $0.member.role == .administrator && $0.member.id != RoomMemberProxyMock.mockMe.userID })?.member else {
|
||||
XCTFail("Expected to find another admin.")
|
||||
return
|
||||
}
|
||||
@@ -205,7 +215,7 @@ class RoomMembersListScreenViewModelTests: XCTestCase {
|
||||
|
||||
// When tapping on yourself in the list.
|
||||
let memberDetailsAction = deferFulfillment(viewModel.actions) { $0.isSelectMember }
|
||||
guard let ownMember = viewModel.state.visibleJoinedMembers.first(where: { $0.id == RoomMemberProxyMock.mockMe.userID }) else {
|
||||
guard let ownMember = viewModel.state.visibleJoinedMembers.first(where: { $0.member.id == RoomMemberProxyMock.mockMe.userID })?.member else {
|
||||
XCTFail("Expected to find own user admin.")
|
||||
return
|
||||
}
|
||||
@@ -225,7 +235,7 @@ class RoomMembersListScreenViewModelTests: XCTestCase {
|
||||
|
||||
// When tapping on a banned member in the list.
|
||||
deferred = deferFulfillment(context.$viewState) { $0.bindings.alertInfo != nil }
|
||||
guard let bannedMember = viewModel.state.visibleBannedMembers.first else {
|
||||
guard let bannedMember = viewModel.state.visibleBannedMembers.first?.member else {
|
||||
XCTFail("Expected to find a banned user.")
|
||||
return
|
||||
}
|
||||
@@ -242,7 +252,7 @@ class RoomMembersListScreenViewModelTests: XCTestCase {
|
||||
let deferred = deferFulfillment(context.$viewState) { !$0.visibleJoinedMembers.isEmpty }
|
||||
try await deferred.fulfill()
|
||||
|
||||
context.send(viewAction: .kickMember(viewModel.state.visibleJoinedMembers[0]))
|
||||
context.send(viewAction: .kickMember(viewModel.state.visibleJoinedMembers[0].member))
|
||||
|
||||
// Calling the mock won't actually change any view state, so sleep instead.
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
@@ -255,7 +265,7 @@ class RoomMembersListScreenViewModelTests: XCTestCase {
|
||||
let deferred = deferFulfillment(context.$viewState) { !$0.visibleJoinedMembers.isEmpty }
|
||||
try await deferred.fulfill()
|
||||
|
||||
context.send(viewAction: .banMember(viewModel.state.visibleJoinedMembers[0]))
|
||||
context.send(viewAction: .banMember(viewModel.state.visibleJoinedMembers[0].member))
|
||||
|
||||
// Calling the mock won't actually change any view state, so sleep instead.
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
@@ -268,7 +278,7 @@ class RoomMembersListScreenViewModelTests: XCTestCase {
|
||||
let deferred = deferFulfillment(context.$viewState) { !$0.visibleJoinedMembers.isEmpty }
|
||||
try await deferred.fulfill()
|
||||
|
||||
context.send(viewAction: .unbanMember(viewModel.state.visibleJoinedMembers[0]))
|
||||
context.send(viewAction: .unbanMember(viewModel.state.visibleJoinedMembers[0].member))
|
||||
|
||||
// Calling the mock won't actually change any view state, so sleep instead.
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
@@ -278,7 +288,8 @@ class RoomMembersListScreenViewModelTests: XCTestCase {
|
||||
|
||||
private func setup(with members: [RoomMemberProxyMock]) {
|
||||
roomProxy = JoinedRoomProxyMock(.init(name: "test", members: members))
|
||||
viewModel = .init(roomProxy: roomProxy,
|
||||
viewModel = .init(clientProxy: ClientProxyMock(.init()),
|
||||
roomProxy: roomProxy,
|
||||
mediaProvider: MediaProviderMock(configuration: .init()),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
analytics: ServiceLocator.shared.analytics)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user