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:
Stefan Ceriu
2025-02-18 08:37:34 +02:00
committed by GitHub
parent b71c93dfaa
commit f77faee981
101 changed files with 722 additions and 305 deletions
@@ -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)
@@ -83,19 +81,7 @@ 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,