Add user verification and verification state violation badges (#4392)
* Move `observeRoomMemberIdentityStateChange` and associated classes to `libs:matrixui` module so they can be reused * Add `EncryptionService.getUserIdentity` method to retrieve not only if the user is verified or not, but in which state they are * Fix `IdentityChangePresenter` after the previous changes * Fix `withFakeLifecycleOwner` and add `testWithLifecycleOwner` helper * Display verified badge in DM top app bar when possible * Display a verification violation warning icon next to the 'People' item in room details screen * Display either a verified badge or a verification violation warning icon next to the room members in the room member list screen * Display either a verified badge or a verification violation warning and withdraw verification button in the room member profile. Generic user profiles won't display verification state anymore since we can't easily track changes in it. * Add preview for room member details screen with verification violation identity state * Add verified and violation badge to the `Profile` list item in room details screen * Update screenshots --------- Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
parent
b0e6b50c79
commit
fd50ce4daf
75 changed files with 889 additions and 364 deletions
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.ui.room
|
||||
|
||||
import androidx.compose.runtime.ProduceStateScope
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.room.roomMembers
|
||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun MatrixRoom.roomMemberIdentityStateChange(): Flow<ImmutableList<RoomMemberIdentityStateChange>> {
|
||||
return syncUpdateFlow
|
||||
.filter {
|
||||
// Room cannot become unencrypted, so we can just apply a filter here.
|
||||
isEncrypted
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.flatMapLatest {
|
||||
combine(identityStateChangesFlow, membersStateFlow) { identityStateChanges, membersState ->
|
||||
identityStateChanges.map { identityStateChange ->
|
||||
val member = membersState.roomMembers()
|
||||
?.find { roomMember -> roomMember.userId == identityStateChange.userId }
|
||||
?.toIdentityRoomMember()
|
||||
?: createDefaultRoomMemberForIdentityChange(identityStateChange.userId)
|
||||
RoomMemberIdentityStateChange(
|
||||
identityRoomMember = member,
|
||||
identityState = identityStateChange.identityState,
|
||||
)
|
||||
}.toPersistentList()
|
||||
}.distinctUntilChanged()
|
||||
}
|
||||
}
|
||||
|
||||
fun ProduceStateScope<PersistentList<RoomMemberIdentityStateChange>>.observeRoomMemberIdentityStateChange(room: MatrixRoom) {
|
||||
room.roomMemberIdentityStateChange()
|
||||
.onEach { roomMemberIdentityStateChanges ->
|
||||
value = roomMemberIdentityStateChanges.toPersistentList()
|
||||
}
|
||||
.launchIn(this)
|
||||
}
|
||||
|
||||
private fun RoomMember.toIdentityRoomMember() = IdentityRoomMember(
|
||||
userId = userId,
|
||||
displayNameOrDefault = displayNameOrDefault,
|
||||
avatarData = getAvatarData(AvatarSize.ComposerAlert),
|
||||
)
|
||||
|
||||
private fun createDefaultRoomMemberForIdentityChange(userId: UserId) = IdentityRoomMember(
|
||||
userId = userId,
|
||||
displayNameOrDefault = userId.extractedDisplayName,
|
||||
avatarData = AvatarData(
|
||||
id = userId.value,
|
||||
name = null,
|
||||
url = null,
|
||||
size = AvatarSize.ComposerAlert,
|
||||
),
|
||||
)
|
||||
|
||||
data class RoomMemberIdentityStateChange(
|
||||
val identityRoomMember: IdentityRoomMember,
|
||||
val identityState: IdentityState,
|
||||
)
|
||||
|
||||
data class IdentityRoomMember(
|
||||
val userId: UserId,
|
||||
val displayNameOrDefault: String,
|
||||
val avatarData: AvatarData,
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue