mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Show a verification badge on the Room Member/User Profile screens. (#3427)
* Add a badge for verified users/room members. * Reorder subviews. * Add a (disabled) button to verify other users. * PR comments. * Update the SDK. * Adopt the SDK changes introduced in matrix-rust-sdk/pull/4100 --------- Co-authored-by: Stefan Ceriu <stefan.ceriu@gmail.com>
This commit is contained in:
@@ -19,10 +19,23 @@ struct UserProfileScreenViewState: BindableState {
|
||||
let isPresentedModally: Bool
|
||||
|
||||
var userProfile: UserProfileProxy?
|
||||
var isVerified: Bool?
|
||||
var permalink: URL?
|
||||
var dmRoomID: String?
|
||||
|
||||
var bindings: UserProfileScreenViewStateBindings
|
||||
|
||||
var showVerifiedBadge: Bool {
|
||||
isVerified == true // We purposely show the badge on your own account for consistency with Web.
|
||||
}
|
||||
|
||||
var showVerificationSection: Bool {
|
||||
isVerified == false && !isOwnUser
|
||||
}
|
||||
|
||||
var verifyButtonTitle: String {
|
||||
L10n.screenRoomMemberDetailsVerifyButtonTitle(userProfile?.displayName ?? "")
|
||||
}
|
||||
}
|
||||
|
||||
struct UserProfileScreenViewStateBindings {
|
||||
|
||||
@@ -42,24 +42,8 @@ class UserProfileScreenViewModel: UserProfileScreenViewModelType, UserProfileScr
|
||||
|
||||
showLoadingIndicator(allowsInteraction: true)
|
||||
Task {
|
||||
defer {
|
||||
hideLoadingIndicator()
|
||||
}
|
||||
|
||||
switch await clientProxy.profile(for: userID) {
|
||||
case .success(let userProfile):
|
||||
state.userProfile = userProfile
|
||||
state.permalink = (try? matrixToUserPermalink(userId: userID)).flatMap(URL.init(string:))
|
||||
switch await clientProxy.directRoomForUserID(userProfile.userID) {
|
||||
case .success(let roomID):
|
||||
state.dmRoomID = roomID
|
||||
case .failure:
|
||||
break
|
||||
}
|
||||
case .failure(let error):
|
||||
state.bindings.alertInfo = .init(id: .unknown)
|
||||
MXLog.error("Failed to find user profile: \(error)")
|
||||
}
|
||||
await loadProfile()
|
||||
hideLoadingIndicator()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +71,32 @@ class UserProfileScreenViewModel: UserProfileScreenViewModelType, UserProfileScr
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func loadProfile() async {
|
||||
async let profileResult = clientProxy.profile(for: state.userID)
|
||||
async let identityResult = clientProxy.userIdentity(for: state.userID)
|
||||
|
||||
switch await profileResult {
|
||||
case .success(let userProfile):
|
||||
state.userProfile = userProfile
|
||||
state.permalink = (try? matrixToUserPermalink(userId: state.userID)).flatMap(URL.init(string:))
|
||||
switch await clientProxy.directRoomForUserID(userProfile.userID) {
|
||||
case .success(let roomID):
|
||||
state.dmRoomID = roomID
|
||||
case .failure:
|
||||
break
|
||||
}
|
||||
case .failure(let error):
|
||||
state.bindings.alertInfo = .init(id: .unknown)
|
||||
MXLog.error("Failed to find user profile: \(error)")
|
||||
}
|
||||
|
||||
if case let .success(.some(identity)) = await identityResult {
|
||||
state.isVerified = identity.isVerified()
|
||||
} else {
|
||||
MXLog.error("Failed to find the user's identity.")
|
||||
}
|
||||
}
|
||||
|
||||
private func displayFullScreenAvatar(_ url: URL) async {
|
||||
guard let userProfile = state.userProfile else { fatalError() }
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ struct UserProfileScreen: View {
|
||||
var body: some View {
|
||||
Form {
|
||||
headerSection
|
||||
|
||||
verificationSection
|
||||
}
|
||||
.compoundList()
|
||||
.navigationTitle(L10n.screenRoomMemberDetailsTitle)
|
||||
@@ -27,6 +29,25 @@ struct UserProfileScreen: View {
|
||||
// MARK: - Private
|
||||
|
||||
@ViewBuilder
|
||||
private var headerSection: some View {
|
||||
if let userProfile = context.viewState.userProfile {
|
||||
AvatarHeaderView(user: userProfile,
|
||||
isVerified: context.viewState.showVerifiedBadge,
|
||||
avatarSize: .user(on: .memberDetails),
|
||||
mediaProvider: context.mediaProvider) { url in
|
||||
context.send(viewAction: .displayAvatar(url))
|
||||
} footer: {
|
||||
otherUserFooter
|
||||
}
|
||||
} else {
|
||||
AvatarHeaderView(user: UserProfileProxy(userID: context.viewState.userID),
|
||||
isVerified: context.viewState.showVerifiedBadge,
|
||||
avatarSize: .user(on: .memberDetails),
|
||||
mediaProvider: context.mediaProvider,
|
||||
footer: { })
|
||||
}
|
||||
}
|
||||
|
||||
private var otherUserFooter: some View {
|
||||
HStack(spacing: 8) {
|
||||
if context.viewState.userProfile != nil, !context.viewState.isOwnUser {
|
||||
@@ -59,20 +80,15 @@ struct UserProfileScreen: View {
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var headerSection: some View {
|
||||
if let userProfile = context.viewState.userProfile {
|
||||
AvatarHeaderView(user: userProfile,
|
||||
avatarSize: .user(on: .memberDetails),
|
||||
mediaProvider: context.mediaProvider) { url in
|
||||
context.send(viewAction: .displayAvatar(url))
|
||||
} footer: {
|
||||
otherUserFooter
|
||||
var verificationSection: some View {
|
||||
if context.viewState.showVerificationSection {
|
||||
Section {
|
||||
ListRow(label: .default(title: context.viewState.verifyButtonTitle,
|
||||
description: L10n.screenRoomMemberDetailsVerifyButtonSubtitle,
|
||||
icon: \.lock),
|
||||
kind: .button { })
|
||||
.disabled(true)
|
||||
}
|
||||
} else {
|
||||
AvatarHeaderView(user: UserProfileProxy(userID: context.viewState.userID),
|
||||
avatarSize: .user(on: .memberDetails),
|
||||
mediaProvider: context.mediaProvider,
|
||||
footer: { })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,10 +107,14 @@ struct UserProfileScreen: View {
|
||||
// MARK: - Previews
|
||||
|
||||
struct UserProfileScreen_Previews: PreviewProvider, TestablePreview {
|
||||
static let otherUserViewModel = makeViewModel(userID: RoomMemberProxyMock.mockDan.userID)
|
||||
static let verifiedUserViewModel = makeViewModel(userID: RoomMemberProxyMock.mockDan.userID)
|
||||
static let otherUserViewModel = makeViewModel(userID: RoomMemberProxyMock.mockAlice.userID)
|
||||
static let accountOwnerViewModel = makeViewModel(userID: RoomMemberProxyMock.mockMe.userID)
|
||||
|
||||
static var previews: some View {
|
||||
UserProfileScreen(context: verifiedUserViewModel.context)
|
||||
.previewDisplayName("Verified User")
|
||||
.snapshotPreferences(delay: 0.25)
|
||||
UserProfileScreen(context: otherUserViewModel.context)
|
||||
.previewDisplayName("Other User")
|
||||
.snapshotPreferences(delay: 0.25)
|
||||
@@ -105,6 +125,10 @@ 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)))
|
||||
}
|
||||
if userID != RoomMemberProxyMock.mockMe.userID {
|
||||
clientProxyMock.directRoomForUserIDReturnValue = .success("roomID")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user