mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-25 10:55:55 +03:00
RoomMember decoration
This commit is contained in:
parent
59abee10f8
commit
cd606ba8a1
12 changed files with 117 additions and 15 deletions
|
@ -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.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.api.util.Optional
|
import im.vector.matrix.android.api.util.Optional
|
||||||
import im.vector.matrix.android.api.util.toOptional
|
import im.vector.matrix.android.api.util.toOptional
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
import io.reactivex.functions.BiFunction
|
import io.reactivex.functions.BiFunction
|
||||||
|
@ -87,8 +88,29 @@ class RxRoom(private val room: Room, private val session: Session) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMemberSummary>> {
|
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMemberSummary>> {
|
||||||
return room.getRoomMembersLive(queryParams).asObservable()
|
val roomMembersObservable = room.getRoomMembersLive(queryParams).asObservable()
|
||||||
.startWith(room.getRoomMembers(queryParams))
|
.startWith(room.getRoomMembers(queryParams))
|
||||||
|
|
||||||
|
// TODO Do it only for room members of the room (switchMap)
|
||||||
|
val cryptoDeviceInfoObservable = session.getLiveCryptoDeviceInfo().asObservable()
|
||||||
|
|
||||||
|
return Observable
|
||||||
|
.combineLatest<List<RoomMemberSummary>, List<CryptoDeviceInfo>, List<RoomMemberSummary>>(
|
||||||
|
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<Optional<EventAnnotationsSummary>> {
|
fun liveAnnotationSummary(eventId: String): Observable<Optional<EventAnnotationsSummary>> {
|
||||||
|
|
|
@ -16,12 +16,16 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.api.session.room.model
|
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
|
* Class representing a simplified version of EventType.STATE_ROOM_MEMBER state event content
|
||||||
*/
|
*/
|
||||||
data class RoomMemberSummary(
|
data class RoomMemberSummary constructor(
|
||||||
val membership: Membership,
|
val membership: Membership,
|
||||||
val userId: String,
|
val userId: String,
|
||||||
val displayName: String? = null,
|
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
|
||||||
)
|
)
|
||||||
|
|
|
@ -667,7 +667,6 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
return if (allTrusted.isEmpty()) {
|
return if (allTrusted.isEmpty()) {
|
||||||
RoomEncryptionTrustLevel.Default
|
RoomEncryptionTrustLevel.Default
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// If one of the verified user as an untrusted device -> warning
|
// If one of the verified user as an untrusted device -> warning
|
||||||
// Green if all devices of all verified users are trusted -> green
|
// Green if all devices of all verified users are trusted -> green
|
||||||
// else black
|
// else black
|
||||||
|
|
|
@ -26,7 +26,8 @@ internal object RoomMemberSummaryMapper {
|
||||||
userId = roomMemberSummaryEntity.userId,
|
userId = roomMemberSummaryEntity.userId,
|
||||||
avatarUrl = roomMemberSummaryEntity.avatarUrl,
|
avatarUrl = roomMemberSummaryEntity.avatarUrl,
|
||||||
displayName = roomMemberSummaryEntity.displayName,
|
displayName = roomMemberSummaryEntity.displayName,
|
||||||
membership = roomMemberSummaryEntity.membership
|
membership = roomMemberSummaryEntity.membership,
|
||||||
|
userEncryptionTrustLevel = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,11 +22,13 @@ import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
|
||||||
import im.vector.matrix.android.api.util.MatrixItem
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||||
import im.vector.riotx.core.extensions.setTextOrHide
|
import im.vector.riotx.core.extensions.setTextOrHide
|
||||||
|
import im.vector.riotx.features.crypto.util.toImageRes
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
|
||||||
@EpoxyModelClass(layout = R.layout.item_profile_matrix_item)
|
@EpoxyModelClass(layout = R.layout.item_profile_matrix_item)
|
||||||
|
@ -34,6 +36,7 @@ abstract class ProfileMatrixItem : VectorEpoxyModel<ProfileMatrixItem.Holder>()
|
||||||
|
|
||||||
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
||||||
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
||||||
|
@EpoxyAttribute var userEncryptionTrustLevel: RoomEncryptionTrustLevel? = null
|
||||||
@EpoxyAttribute var clickListener: View.OnClickListener? = null
|
@EpoxyAttribute var clickListener: View.OnClickListener? = null
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
|
@ -43,11 +46,13 @@ abstract class ProfileMatrixItem : VectorEpoxyModel<ProfileMatrixItem.Holder>()
|
||||||
holder.titleView.text = bestName
|
holder.titleView.text = bestName
|
||||||
holder.subtitleView.setTextOrHide(matrixId)
|
holder.subtitleView.setTextOrHide(matrixId)
|
||||||
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
||||||
|
holder.avatarDecorationImageView.setImageResource(userEncryptionTrustLevel.toImageRes())
|
||||||
}
|
}
|
||||||
|
|
||||||
class Holder : VectorEpoxyHolder() {
|
class Holder : VectorEpoxyHolder() {
|
||||||
val titleView by bind<TextView>(R.id.matrixItemTitle)
|
val titleView by bind<TextView>(R.id.matrixItemTitle)
|
||||||
val subtitleView by bind<TextView>(R.id.matrixItemSubtitle)
|
val subtitleView by bind<TextView>(R.id.matrixItemSubtitle)
|
||||||
val avatarImageView by bind<ImageView>(R.id.matrixItemAvatar)
|
val avatarImageView by bind<ImageView>(R.id.matrixItemAvatar)
|
||||||
|
val avatarDecorationImageView by bind<ImageView>(R.id.matrixItemAvatarDecoration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,11 +79,17 @@ class RoomMemberProfileController @Inject constructor(
|
||||||
// Cross signing is enabled for this user
|
// Cross signing is enabled for this user
|
||||||
if (state.userMXCrossSigningInfo.isTrusted()) {
|
if (state.userMXCrossSigningInfo.isTrusted()) {
|
||||||
// User is trusted
|
// User is trusted
|
||||||
val icon = if (state.allDevicesAreTrusted.invoke() == true) R.drawable.ic_shield_trusted
|
val icon = if (state.allDevicesAreTrusted) {
|
||||||
else R.drawable.ic_shield_warning
|
R.drawable.ic_shield_trusted
|
||||||
|
} else {
|
||||||
|
R.drawable.ic_shield_warning
|
||||||
|
}
|
||||||
|
|
||||||
val titleRes = if (state.allDevicesAreTrusted.invoke() == true) R.string.verification_profile_verified
|
val titleRes = if (state.allDevicesAreTrusted) {
|
||||||
else R.string.verification_profile_warning
|
R.string.verification_profile_verified
|
||||||
|
} else {
|
||||||
|
R.string.verification_profile_warning
|
||||||
|
}
|
||||||
|
|
||||||
buildProfileAction(
|
buildProfileAction(
|
||||||
id = "learn_more",
|
id = "learn_more",
|
||||||
|
|
|
@ -20,6 +20,7 @@ package im.vector.riotx.features.roommemberprofile
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import com.airbnb.mvrx.Fail
|
import com.airbnb.mvrx.Fail
|
||||||
import com.airbnb.mvrx.Incomplete
|
import com.airbnb.mvrx.Incomplete
|
||||||
import com.airbnb.mvrx.Success
|
import com.airbnb.mvrx.Success
|
||||||
|
@ -79,8 +80,13 @@ class RoomMemberProfileFragment @Inject constructor(
|
||||||
memberProfileStateView.contentView = memberProfileInfoContainer
|
memberProfileStateView.contentView = memberProfileInfoContainer
|
||||||
matrixProfileRecyclerView.configureWith(roomMemberProfileController, hasFixedSize = true)
|
matrixProfileRecyclerView.configureWith(roomMemberProfileController, hasFixedSize = true)
|
||||||
roomMemberProfileController.callback = this
|
roomMemberProfileController.callback = this
|
||||||
appBarStateChangeListener = MatrixItemAppBarStateChangeListener(headerView, listOf(matrixProfileToolbarAvatarImageView,
|
appBarStateChangeListener = MatrixItemAppBarStateChangeListener(headerView,
|
||||||
matrixProfileToolbarTitleView))
|
listOf(
|
||||||
|
matrixProfileToolbarAvatarImageView,
|
||||||
|
matrixProfileToolbarTitleView,
|
||||||
|
matrixProfileDecorationToolbarAvatarImageView
|
||||||
|
)
|
||||||
|
)
|
||||||
matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener)
|
matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener)
|
||||||
viewModel.observeViewEvents {
|
viewModel.observeViewEvents {
|
||||||
when (it) {
|
when (it) {
|
||||||
|
@ -133,6 +139,37 @@ class RoomMemberProfileFragment @Inject constructor(
|
||||||
matrixProfileToolbarTitleView.text = bestName
|
matrixProfileToolbarTitleView.text = bestName
|
||||||
avatarRenderer.render(userMatrixItem, memberProfileAvatarView)
|
avatarRenderer.render(userMatrixItem, memberProfileAvatarView)
|
||||||
avatarRenderer.render(userMatrixItem, matrixProfileToolbarAvatarImageView)
|
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())
|
memberProfilePowerLevelView.setTextOrHide(state.userPowerLevelString())
|
||||||
|
|
|
@ -104,10 +104,16 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
|
||||||
|
|
||||||
session.rx().liveUserCryptoDevices(initialState.userId)
|
session.rx().liveUserCryptoDevices(initialState.userId)
|
||||||
.map {
|
.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 {
|
.execute { it ->
|
||||||
copy(allDevicesAreTrusted = it)
|
copy(
|
||||||
|
allDevicesAreTrusted = it()?.first == true,
|
||||||
|
allDevicesAreCrossSignedTrusted = it()?.second == true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
session.rx().liveCrossSigningInfo(initialState.userId)
|
session.rx().liveCrossSigningInfo(initialState.userId)
|
||||||
|
|
|
@ -35,7 +35,8 @@ data class RoomMemberProfileViewState(
|
||||||
val userPowerLevelString: Async<String> = Uninitialized,
|
val userPowerLevelString: Async<String> = Uninitialized,
|
||||||
val userMatrixItem: Async<MatrixItem> = Uninitialized,
|
val userMatrixItem: Async<MatrixItem> = Uninitialized,
|
||||||
val userMXCrossSigningInfo: MXCrossSigningInfo? = null,
|
val userMXCrossSigningInfo: MXCrossSigningInfo? = null,
|
||||||
val allDevicesAreTrusted: Async<Boolean> = Uninitialized
|
val allDevicesAreTrusted: Boolean = false,
|
||||||
|
val allDevicesAreCrossSignedTrusted: Boolean = false
|
||||||
) : MvRxState {
|
) : MvRxState {
|
||||||
|
|
||||||
constructor(args: RoomMemberProfileArgs) : this(roomId = args.roomId, userId = args.userId)
|
constructor(args: RoomMemberProfileArgs) : this(roomId = args.roomId, userId = args.userId)
|
||||||
|
|
|
@ -62,6 +62,7 @@ class RoomMemberListController @Inject constructor(
|
||||||
id(roomMember.userId)
|
id(roomMember.userId)
|
||||||
matrixItem(roomMember.toMatrixItem())
|
matrixItem(roomMember.toMatrixItem())
|
||||||
avatarRenderer(avatarRenderer)
|
avatarRenderer(avatarRenderer)
|
||||||
|
userEncryptionTrustLevel(roomMember.userEncryptionTrustLevel)
|
||||||
clickListener { _ ->
|
clickListener { _ ->
|
||||||
callback?.onRoomMemberClicked(roomMember)
|
callback?.onRoomMemberClicked(roomMember)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,16 @@
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:src="@tools:sample/avatars" />
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/matrixItemAvatarDecoration"
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
app:layout_constraintCircle="@+id/matrixItemAvatar"
|
||||||
|
app:layout_constraintCircleAngle="135"
|
||||||
|
app:layout_constraintCircleRadius="16dp"
|
||||||
|
tools:ignore="MissingConstraints"
|
||||||
|
tools:src="@drawable/ic_shield_trusted" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/matrixItemTitle"
|
android:id="@+id/matrixItemTitle"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
|
@ -29,6 +29,16 @@
|
||||||
app:layout_constraintVertical_chainStyle="spread_inside"
|
app:layout_constraintVertical_chainStyle="spread_inside"
|
||||||
tools:src="@tools:sample/avatars" />
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/memberProfileDecorationImageView"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
app:layout_constraintCircle="@+id/memberProfileAvatarView"
|
||||||
|
app:layout_constraintCircleAngle="135"
|
||||||
|
app:layout_constraintCircleRadius="64dp"
|
||||||
|
tools:ignore="MissingConstraints"
|
||||||
|
tools:src="@drawable/ic_shield_trusted" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/memberProfileNameView"
|
android:id="@+id/memberProfileNameView"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
Loading…
Reference in a new issue