From cd606ba8a107a4461baade1a1c4343be91a15f37 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Sat, 1 Feb 2020 11:36:07 +0100 Subject: [PATCH] RoomMember decoration --- .../main/java/im/vector/matrix/rx/RxRoom.kt | 24 ++++++++++- .../session/room/model/RoomMemberSummary.kt | 8 +++- .../DefaultCrossSigningService.kt | 1 - .../mapper/RoomMemberSummaryMapper.kt | 3 +- .../core/epoxy/profiles/ProfileMatrixItem.kt | 5 +++ .../RoomMemberProfileController.kt | 14 +++++-- .../RoomMemberProfileFragment.kt | 41 ++++++++++++++++++- .../RoomMemberProfileViewModel.kt | 12 ++++-- .../RoomMemberProfileViewState.kt | 3 +- .../members/RoomMemberListController.kt | 1 + .../res/layout/item_profile_matrix_item.xml | 10 +++++ .../view_stub_room_member_profile_header.xml | 10 +++++ 12 files changed, 117 insertions(+), 15 deletions(-) diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt index a0301e5cab..6ca0b55fd7 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt @@ -30,6 +30,7 @@ import im.vector.matrix.android.api.session.room.send.UserDraft import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.toOptional +import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo import io.reactivex.Observable import io.reactivex.Single import io.reactivex.functions.BiFunction @@ -87,8 +88,29 @@ class RxRoom(private val room: Room, private val session: Session) { } fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable> { - return room.getRoomMembersLive(queryParams).asObservable() + val roomMembersObservable = room.getRoomMembersLive(queryParams).asObservable() .startWith(room.getRoomMembers(queryParams)) + + // TODO Do it only for room members of the room (switchMap) + val cryptoDeviceInfoObservable = session.getLiveCryptoDeviceInfo().asObservable() + + return Observable + .combineLatest, List, List>( + roomMembersObservable, + cryptoDeviceInfoObservable, + BiFunction { summaries, _ -> + summaries.map { + if (room.isEncrypted()) { + it.copy( + // Get the trust level of a virtual room with only this user + userEncryptionTrustLevel = session.getCrossSigningService().getTrustLevelForUsers(listOf(it.userId)) + ) + } else { + it + } + } + } + ) } fun liveAnnotationSummary(eventId: String): Observable> { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMemberSummary.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMemberSummary.kt index 17768362b2..d3b7f3f026 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMemberSummary.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomMemberSummary.kt @@ -16,12 +16,16 @@ package im.vector.matrix.android.api.session.room.model +import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel + /** * Class representing a simplified version of EventType.STATE_ROOM_MEMBER state event content */ -data class RoomMemberSummary( +data class RoomMemberSummary constructor( val membership: Membership, val userId: String, val displayName: String? = null, - val avatarUrl: String? = null + val avatarUrl: String? = null, + // TODO Warning: Will not be populated if not using RxRoom + val userEncryptionTrustLevel: RoomEncryptionTrustLevel? = null ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt index b987dd6818..1c5a549a05 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt @@ -667,7 +667,6 @@ internal class DefaultCrossSigningService @Inject constructor( return if (allTrusted.isEmpty()) { RoomEncryptionTrustLevel.Default } else { - // If one of the verified user as an untrusted device -> warning // Green if all devices of all verified users are trusted -> green // else black diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomMemberSummaryMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomMemberSummaryMapper.kt index 470772a40e..5e8db4cf84 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomMemberSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomMemberSummaryMapper.kt @@ -26,7 +26,8 @@ internal object RoomMemberSummaryMapper { userId = roomMemberSummaryEntity.userId, avatarUrl = roomMemberSummaryEntity.avatarUrl, displayName = roomMemberSummaryEntity.displayName, - membership = roomMemberSummaryEntity.membership + membership = roomMemberSummaryEntity.membership, + userEncryptionTrustLevel = null ) } } diff --git a/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileMatrixItem.kt b/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileMatrixItem.kt index 4fe65748ce..5d9a89be34 100644 --- a/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileMatrixItem.kt +++ b/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileMatrixItem.kt @@ -22,11 +22,13 @@ import android.widget.ImageView import android.widget.TextView import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass +import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel import im.vector.matrix.android.api.util.MatrixItem import im.vector.riotx.R import im.vector.riotx.core.epoxy.VectorEpoxyHolder import im.vector.riotx.core.epoxy.VectorEpoxyModel import im.vector.riotx.core.extensions.setTextOrHide +import im.vector.riotx.features.crypto.util.toImageRes import im.vector.riotx.features.home.AvatarRenderer @EpoxyModelClass(layout = R.layout.item_profile_matrix_item) @@ -34,6 +36,7 @@ abstract class ProfileMatrixItem : VectorEpoxyModel() @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer @EpoxyAttribute lateinit var matrixItem: MatrixItem + @EpoxyAttribute var userEncryptionTrustLevel: RoomEncryptionTrustLevel? = null @EpoxyAttribute var clickListener: View.OnClickListener? = null override fun bind(holder: Holder) { @@ -43,11 +46,13 @@ abstract class ProfileMatrixItem : VectorEpoxyModel() holder.titleView.text = bestName holder.subtitleView.setTextOrHide(matrixId) avatarRenderer.render(matrixItem, holder.avatarImageView) + holder.avatarDecorationImageView.setImageResource(userEncryptionTrustLevel.toImageRes()) } class Holder : VectorEpoxyHolder() { val titleView by bind(R.id.matrixItemTitle) val subtitleView by bind(R.id.matrixItemSubtitle) val avatarImageView by bind(R.id.matrixItemAvatar) + val avatarDecorationImageView by bind(R.id.matrixItemAvatarDecoration) } } diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileController.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileController.kt index 5a18588daa..b98235b29a 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileController.kt @@ -79,11 +79,17 @@ class RoomMemberProfileController @Inject constructor( // Cross signing is enabled for this user if (state.userMXCrossSigningInfo.isTrusted()) { // User is trusted - val icon = if (state.allDevicesAreTrusted.invoke() == true) R.drawable.ic_shield_trusted - else R.drawable.ic_shield_warning + val icon = if (state.allDevicesAreTrusted) { + R.drawable.ic_shield_trusted + } else { + R.drawable.ic_shield_warning + } - val titleRes = if (state.allDevicesAreTrusted.invoke() == true) R.string.verification_profile_verified - else R.string.verification_profile_warning + val titleRes = if (state.allDevicesAreTrusted) { + R.string.verification_profile_verified + } else { + R.string.verification_profile_warning + } buildProfileAction( id = "learn_more", diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileFragment.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileFragment.kt index 5a711f5cdb..cce105619a 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileFragment.kt @@ -20,6 +20,7 @@ package im.vector.riotx.features.roommemberprofile import android.os.Bundle import android.os.Parcelable import android.view.View +import androidx.core.view.isVisible import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Incomplete import com.airbnb.mvrx.Success @@ -79,8 +80,13 @@ class RoomMemberProfileFragment @Inject constructor( memberProfileStateView.contentView = memberProfileInfoContainer matrixProfileRecyclerView.configureWith(roomMemberProfileController, hasFixedSize = true) roomMemberProfileController.callback = this - appBarStateChangeListener = MatrixItemAppBarStateChangeListener(headerView, listOf(matrixProfileToolbarAvatarImageView, - matrixProfileToolbarTitleView)) + appBarStateChangeListener = MatrixItemAppBarStateChangeListener(headerView, + listOf( + matrixProfileToolbarAvatarImageView, + matrixProfileToolbarTitleView, + matrixProfileDecorationToolbarAvatarImageView + ) + ) matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener) viewModel.observeViewEvents { when (it) { @@ -133,6 +139,37 @@ class RoomMemberProfileFragment @Inject constructor( matrixProfileToolbarTitleView.text = bestName avatarRenderer.render(userMatrixItem, memberProfileAvatarView) avatarRenderer.render(userMatrixItem, matrixProfileToolbarAvatarImageView) + + if (state.isRoomEncrypted) { + memberProfileDecorationImageView.isVisible = true + if (state.userMXCrossSigningInfo != null) { + // Cross signing is enabled for this user + val icon = if (state.userMXCrossSigningInfo.isTrusted()) { + // User is trusted + if (state.allDevicesAreCrossSignedTrusted) { + R.drawable.ic_shield_trusted + } else { + R.drawable.ic_shield_warning + } + } else { + R.drawable.ic_shield_black + } + + memberProfileDecorationImageView.setImageResource(icon) + matrixProfileDecorationToolbarAvatarImageView.setImageResource(icon) + } else { + // Legacy + if (state.allDevicesAreTrusted) { + memberProfileDecorationImageView.setImageResource(R.drawable.ic_shield_trusted) + matrixProfileDecorationToolbarAvatarImageView.setImageResource(R.drawable.ic_shield_trusted) + } else { + memberProfileDecorationImageView.setImageResource(R.drawable.ic_shield_warning) + matrixProfileDecorationToolbarAvatarImageView.setImageResource(R.drawable.ic_shield_warning) + } + } + } else { + memberProfileDecorationImageView.isVisible = false + } } } memberProfilePowerLevelView.setTextOrHide(state.userPowerLevelString()) diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewModel.kt index f6fe353f29..1820262b49 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -104,10 +104,16 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v session.rx().liveUserCryptoDevices(initialState.userId) .map { - it.fold(true, { prev, dev -> prev && dev.isVerified }) + Pair( + it.fold(true, { prev, dev -> prev && dev.isVerified }), + it.fold(true, { prev, dev -> prev && (dev.trustLevel?.crossSigningVerified == true) }) + ) } - .execute { - copy(allDevicesAreTrusted = it) + .execute { it -> + copy( + allDevicesAreTrusted = it()?.first == true, + allDevicesAreCrossSignedTrusted = it()?.second == true + ) } session.rx().liveCrossSigningInfo(initialState.userId) diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewState.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewState.kt index b9044b1d36..a5c140d0ab 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewState.kt @@ -35,7 +35,8 @@ data class RoomMemberProfileViewState( val userPowerLevelString: Async = Uninitialized, val userMatrixItem: Async = Uninitialized, val userMXCrossSigningInfo: MXCrossSigningInfo? = null, - val allDevicesAreTrusted: Async = Uninitialized + val allDevicesAreTrusted: Boolean = false, + val allDevicesAreCrossSignedTrusted: Boolean = false ) : MvRxState { constructor(args: RoomMemberProfileArgs) : this(roomId = args.roomId, userId = args.userId) diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListController.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListController.kt index 0d838e9c72..bd3dd06bd3 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListController.kt @@ -62,6 +62,7 @@ class RoomMemberListController @Inject constructor( id(roomMember.userId) matrixItem(roomMember.toMatrixItem()) avatarRenderer(avatarRenderer) + userEncryptionTrustLevel(roomMember.userEncryptionTrustLevel) clickListener { _ -> callback?.onRoomMemberClicked(roomMember) } diff --git a/vector/src/main/res/layout/item_profile_matrix_item.xml b/vector/src/main/res/layout/item_profile_matrix_item.xml index cdca2341e8..87f2f8de27 100644 --- a/vector/src/main/res/layout/item_profile_matrix_item.xml +++ b/vector/src/main/res/layout/item_profile_matrix_item.xml @@ -25,6 +25,16 @@ app:layout_constraintTop_toTopOf="parent" tools:src="@tools:sample/avatars" /> + + + +