mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-02-18 04:50:08 +03:00
Merge pull request #924 from vector-im/feature/crossing_fix_2
Feature/crossing fix 2
This commit is contained in:
commit
8a9bd97a88
25 changed files with 274 additions and 71 deletions
|
@ -41,6 +41,9 @@ dependencies {
|
||||||
// Paging
|
// Paging
|
||||||
implementation "androidx.paging:paging-runtime-ktx:2.1.0"
|
implementation "androidx.paging:paging-runtime-ktx:2.1.0"
|
||||||
|
|
||||||
|
// Logging
|
||||||
|
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||||
|
|
|
@ -30,9 +30,11 @@ 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
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
class RxRoom(private val room: Room, private val session: Session) {
|
class RxRoom(private val room: Room, private val session: Session) {
|
||||||
|
|
||||||
|
@ -40,39 +42,45 @@ class RxRoom(private val room: Room, private val session: Session) {
|
||||||
val summaryObservable = room.getRoomSummaryLive()
|
val summaryObservable = room.getRoomSummaryLive()
|
||||||
.asObservable()
|
.asObservable()
|
||||||
.startWith(room.roomSummary().toOptional())
|
.startWith(room.roomSummary().toOptional())
|
||||||
|
.doOnNext { Timber.d("RX: summary emitted for: ${it.getOrNull()?.roomId}") }
|
||||||
|
|
||||||
val memberIdsChangeObservable = summaryObservable
|
val memberIdsChangeObservable = summaryObservable
|
||||||
.map {
|
.map {
|
||||||
it.getOrNull()?.let { roomSummary ->
|
it.getOrNull()?.let { roomSummary ->
|
||||||
if (roomSummary.isEncrypted) {
|
if (roomSummary.isEncrypted) {
|
||||||
// Return the list of other users
|
// Return the list of other users
|
||||||
roomSummary.otherMemberIds
|
roomSummary.otherMemberIds + listOf(session.myUserId)
|
||||||
} else {
|
} else {
|
||||||
// Return an empty list, the room is not encrypted
|
// Return an empty list, the room is not encrypted
|
||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
}.orEmpty()
|
}.orEmpty()
|
||||||
}.distinctUntilChanged()
|
}.distinctUntilChanged()
|
||||||
|
.doOnNext { Timber.d("RX: memberIds emitted. Size: ${it.size}") }
|
||||||
|
|
||||||
// Observe the device info of the users in the room
|
// Observe the device info of the users in the room
|
||||||
val cryptoDeviceInfoObservable = memberIdsChangeObservable
|
val cryptoDeviceInfoObservable = memberIdsChangeObservable
|
||||||
.switchMap { otherUserIds ->
|
.switchMap { membersIds ->
|
||||||
session.getLiveCryptoDeviceInfo(otherUserIds)
|
session.getLiveCryptoDeviceInfo(membersIds)
|
||||||
.asObservable()
|
.asObservable()
|
||||||
.map {
|
.map {
|
||||||
// If any key change, emit the userIds list
|
// If any key change, emit the userIds list
|
||||||
otherUserIds
|
membersIds
|
||||||
}
|
}
|
||||||
|
.startWith(membersIds)
|
||||||
|
.doOnNext { Timber.d("RX: CryptoDeviceInfo emitted. Size: ${it.size}") }
|
||||||
}
|
}
|
||||||
|
.doOnNext { Timber.d("RX: cryptoDeviceInfo emitted 2. Size: ${it.size}") }
|
||||||
|
|
||||||
val roomEncryptionTrustLevelObservable = cryptoDeviceInfoObservable
|
val roomEncryptionTrustLevelObservable = cryptoDeviceInfoObservable
|
||||||
.map { otherUserIds ->
|
.map { userIds ->
|
||||||
if (otherUserIds.isEmpty()) {
|
if (userIds.isEmpty()) {
|
||||||
Optional<RoomEncryptionTrustLevel>(null)
|
Optional<RoomEncryptionTrustLevel>(null)
|
||||||
} else {
|
} else {
|
||||||
session.getCrossSigningService().getTrustLevelForUsers(otherUserIds).toOptional()
|
session.getCrossSigningService().getTrustLevelForUsers(userIds).toOptional()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.doOnNext { Timber.d("RX: roomEncryptionTrustLevel emitted: ${it.getOrNull()?.name}") }
|
||||||
|
|
||||||
return Observable
|
return Observable
|
||||||
.combineLatest<Optional<RoomSummary>, Optional<RoomEncryptionTrustLevel>, Optional<RoomSummary>>(
|
.combineLatest<Optional<RoomSummary>, Optional<RoomEncryptionTrustLevel>, Optional<RoomSummary>>(
|
||||||
|
@ -84,11 +92,37 @@ class RxRoom(private val room: Room, private val session: Session) {
|
||||||
).toOptional()
|
).toOptional()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
.doOnNext { Timber.d("RX: final room summary emitted for ${it.getOrNull()?.roomId}") }
|
||||||
}
|
}
|
||||||
|
|
||||||
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))
|
||||||
|
.doOnNext { Timber.d("RX: room members emitted. Size: ${it.size}") }
|
||||||
|
|
||||||
|
// TODO Do it only for room members of the room (switchMap)
|
||||||
|
val cryptoDeviceInfoObservable = session.getLiveCryptoDeviceInfo().asObservable()
|
||||||
|
.startWith(emptyList<CryptoDeviceInfo>())
|
||||||
|
.doOnNext { Timber.d("RX: cryptoDeviceInfo emitted. Size: ${it.size}") }
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.doOnNext { Timber.d("RX: final room members emitted. Size: ${it.size}") }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveAnnotationSummary(eventId: String): Observable<Optional<EventAnnotationsSummary>> {
|
fun liveAnnotationSummary(eventId: String): Observable<Optional<EventAnnotationsSummary>> {
|
||||||
|
|
|
@ -34,14 +34,18 @@ 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
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
class RxSession(private val session: Session) {
|
class RxSession(private val session: Session) {
|
||||||
|
|
||||||
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
|
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
|
||||||
val summariesObservable = session.getRoomSummariesLive(queryParams).asObservable()
|
val summariesObservable = session.getRoomSummariesLive(queryParams).asObservable()
|
||||||
.startWith(session.getRoomSummaries(queryParams))
|
.startWith(session.getRoomSummaries(queryParams))
|
||||||
|
.doOnNext { Timber.d("RX: summaries emitted: size: ${it.size}") }
|
||||||
|
|
||||||
val cryptoDeviceInfoObservable = session.getLiveCryptoDeviceInfo().asObservable()
|
val cryptoDeviceInfoObservable = session.getLiveCryptoDeviceInfo().asObservable()
|
||||||
|
.startWith(emptyList<CryptoDeviceInfo>())
|
||||||
|
.doOnNext { Timber.d("RX: crypto device info emitted: size: ${it.size}") }
|
||||||
|
|
||||||
return Observable
|
return Observable
|
||||||
.combineLatest<List<RoomSummary>, List<CryptoDeviceInfo>, List<RoomSummary>>(
|
.combineLatest<List<RoomSummary>, List<CryptoDeviceInfo>, List<RoomSummary>>(
|
||||||
|
@ -51,7 +55,8 @@ class RxSession(private val session: Session) {
|
||||||
summaries.map {
|
summaries.map {
|
||||||
if (it.isEncrypted) {
|
if (it.isEncrypted) {
|
||||||
it.copy(
|
it.copy(
|
||||||
roomEncryptionTrustLevel = session.getCrossSigningService().getTrustLevelForUsers(it.otherMemberIds)
|
roomEncryptionTrustLevel = session.getCrossSigningService()
|
||||||
|
.getTrustLevelForUsers(it.otherMemberIds + session.myUserId)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
it
|
it
|
||||||
|
@ -59,6 +64,7 @@ class RxSession(private val session: Session) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
.doOnNext { Timber.d("RX: final summaries emitted: size: ${it.size}") }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Observable<List<GroupSummary>> {
|
fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Observable<List<GroupSummary>> {
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -20,6 +20,7 @@ import im.vector.matrix.android.api.session.events.model.Content
|
||||||
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
||||||
|
|
||||||
interface MessageContent {
|
interface MessageContent {
|
||||||
|
// TODO Rename to msgType
|
||||||
val type: String
|
val type: String
|
||||||
val body: String
|
val body: String
|
||||||
val relatesTo: RelationDefaultContent?
|
val relatesTo: RelationDefaultContent?
|
||||||
|
|
|
@ -659,16 +659,18 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getTrustLevelForUsers(userIds: List<String>): RoomEncryptionTrustLevel {
|
override fun getTrustLevelForUsers(userIds: List<String>): RoomEncryptionTrustLevel {
|
||||||
val atLeastOneTrusted = userIds
|
val allTrusted = userIds
|
||||||
.filter { it != userId }
|
.filter { getUserCrossSigningKeys(it)?.isTrusted() == true }
|
||||||
.map { getUserCrossSigningKeys(it) }
|
|
||||||
.any { it?.isTrusted() == true }
|
|
||||||
|
|
||||||
return if (!atLeastOneTrusted) {
|
val allUsersAreVerified = userIds.size == allTrusted.size
|
||||||
|
|
||||||
|
return if (allTrusted.isEmpty()) {
|
||||||
RoomEncryptionTrustLevel.Default
|
RoomEncryptionTrustLevel.Default
|
||||||
} else {
|
} else {
|
||||||
// I have verified at least one other user
|
// If one of the verified user as an untrusted device -> warning
|
||||||
val allDevices = userIds.mapNotNull {
|
// Green if all devices of all verified users are trusted -> green
|
||||||
|
// else black
|
||||||
|
val allDevices = allTrusted.mapNotNull {
|
||||||
cryptoStore.getUserDeviceList(it)
|
cryptoStore.getUserDeviceList(it)
|
||||||
}.flatten()
|
}.flatten()
|
||||||
if (getMyCrossSigningKeys() != null) {
|
if (getMyCrossSigningKeys() != null) {
|
||||||
|
@ -676,14 +678,14 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||||
if (hasWarning) {
|
if (hasWarning) {
|
||||||
RoomEncryptionTrustLevel.Warning
|
RoomEncryptionTrustLevel.Warning
|
||||||
} else {
|
} else {
|
||||||
RoomEncryptionTrustLevel.Trusted
|
if (allUsersAreVerified) RoomEncryptionTrustLevel.Trusted else RoomEncryptionTrustLevel.Default
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val hasWarningLegacy = allDevices.any { !it.isVerified }
|
val hasWarningLegacy = allDevices.any { !it.isVerified }
|
||||||
if (hasWarningLegacy) {
|
if (hasWarningLegacy) {
|
||||||
RoomEncryptionTrustLevel.Warning
|
RoomEncryptionTrustLevel.Warning
|
||||||
} else {
|
} else {
|
||||||
RoomEncryptionTrustLevel.Trusted
|
if (allUsersAreVerified) RoomEncryptionTrustLevel.Trusted else RoomEncryptionTrustLevel.Default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@ internal class RoomSummaryUpdater @Inject constructor(
|
||||||
|
|
||||||
// TODO: maybe allow user of SDK to give that list
|
// TODO: maybe allow user of SDK to give that list
|
||||||
private val PREVIEWABLE_TYPES = listOf(
|
private val PREVIEWABLE_TYPES = listOf(
|
||||||
|
// TODO filter message type (KEY_VERIFICATION_READY, etc.)
|
||||||
EventType.MESSAGE,
|
EventType.MESSAGE,
|
||||||
EventType.STATE_ROOM_NAME,
|
EventType.STATE_ROOM_NAME,
|
||||||
EventType.STATE_ROOM_TOPIC,
|
EventType.STATE_ROOM_TOPIC,
|
||||||
|
|
|
@ -369,7 +369,8 @@ dependencies {
|
||||||
implementation "androidx.emoji:emoji-appcompat:1.0.0"
|
implementation "androidx.emoji:emoji-appcompat:1.0.0"
|
||||||
|
|
||||||
// QR-code
|
// QR-code
|
||||||
implementation 'com.google.zxing:core:3.4.0'
|
// Stick to 3.3.3 because of https://github.com/zxing/zxing/issues/1170
|
||||||
|
implementation 'com.google.zxing:core:3.3.3'
|
||||||
implementation 'me.dm7.barcodescanner:zxing:1.9.13'
|
implementation 'me.dm7.barcodescanner:zxing:1.9.13'
|
||||||
|
|
||||||
// TESTS
|
// TESTS
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,5 +23,5 @@ sealed class RoomMemberProfileAction : VectorViewModelAction {
|
||||||
|
|
||||||
object RetryFetchingInfo: RoomMemberProfileAction()
|
object RetryFetchingInfo: RoomMemberProfileAction()
|
||||||
object IgnoreUser: RoomMemberProfileAction()
|
object IgnoreUser: RoomMemberProfileAction()
|
||||||
data class VerifyUser(val userId: String? = null, val roomId: String? = null): RoomMemberProfileAction()
|
data class VerifyUser(val userId: String? = null, val roomId: String? = null, val canCrossSign: Boolean? = true): RoomMemberProfileAction()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
@ -106,6 +112,15 @@ class RoomMemberProfileController @Inject constructor(
|
||||||
divider = false,
|
divider = false,
|
||||||
action = { callback?.onTapVerify() }
|
action = { callback?.onTapVerify() }
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
buildProfileAction(
|
||||||
|
id = "learn_more",
|
||||||
|
title = stringProvider.getString(R.string.room_profile_section_security_learn_more),
|
||||||
|
dividerColor = dividerColor,
|
||||||
|
editable = false,
|
||||||
|
divider = false,
|
||||||
|
action = { callback?.onShowDeviceListNoCrossSigning() }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
genericFooterItem {
|
genericFooterItem {
|
||||||
|
|
|
@ -20,6 +20,8 @@ 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.appcompat.app.AlertDialog
|
||||||
|
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 +81,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) {
|
||||||
|
@ -94,9 +101,22 @@ class RoomMemberProfileFragment @Inject constructor(
|
||||||
is Success -> {
|
is Success -> {
|
||||||
when (val action = async.invoke()) {
|
when (val action = async.invoke()) {
|
||||||
is RoomMemberProfileAction.VerifyUser -> {
|
is RoomMemberProfileAction.VerifyUser -> {
|
||||||
VerificationBottomSheet
|
if (action.canCrossSign == true) {
|
||||||
.withArgs(roomId = null, otherUserId = action.userId!!)
|
VerificationBottomSheet
|
||||||
.show(parentFragmentManager, "VERIF")
|
.withArgs(roomId = null, otherUserId = action.userId!!)
|
||||||
|
.show(parentFragmentManager, "VERIF")
|
||||||
|
} else {
|
||||||
|
AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle(R.string.dialog_title_warning)
|
||||||
|
.setMessage(R.string.verify_cannot_cross_sign)
|
||||||
|
.setPositiveButton(R.string.verification_profile_verify) { _, _ ->
|
||||||
|
VerificationBottomSheet
|
||||||
|
.withArgs(roomId = null, otherUserId = action.userId!!)
|
||||||
|
.show(parentFragmentManager, "VERIF")
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,6 +153,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())
|
||||||
|
|
|
@ -24,6 +24,7 @@ import com.airbnb.mvrx.Async
|
||||||
import com.airbnb.mvrx.FragmentViewModelContext
|
import com.airbnb.mvrx.FragmentViewModelContext
|
||||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
import com.airbnb.mvrx.Success
|
import com.airbnb.mvrx.Success
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
import com.airbnb.mvrx.ViewModelContext
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
|
@ -85,7 +86,12 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setState { copy(isMine = session.myUserId == this.userId) }
|
setState {
|
||||||
|
copy(
|
||||||
|
isMine = session.myUserId == this.userId,
|
||||||
|
userMatrixItem = room?.getRoomMember(initialState.userId)?.toMatrixItem()?.let { Success(it) } ?: Uninitialized
|
||||||
|
)
|
||||||
|
}
|
||||||
observeIgnoredState()
|
observeIgnoredState()
|
||||||
viewModelScope.launch(Dispatchers.Main) {
|
viewModelScope.launch(Dispatchers.Main) {
|
||||||
// Do we have a room member for this id.
|
// Do we have a room member for this id.
|
||||||
|
@ -101,20 +107,26 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
|
||||||
observeRoomMemberSummary(room)
|
observeRoomMemberSummary(room)
|
||||||
observeRoomSummaryAndPowerLevels(room)
|
observeRoomSummaryAndPowerLevels(room)
|
||||||
}
|
}
|
||||||
|
|
||||||
session.rx().liveUserCryptoDevices(initialState.userId)
|
|
||||||
.map {
|
|
||||||
it.fold(true, { prev, dev -> prev && dev.isVerified })
|
|
||||||
}
|
|
||||||
.execute {
|
|
||||||
copy(allDevicesAreTrusted = it)
|
|
||||||
}
|
|
||||||
|
|
||||||
session.rx().liveCrossSigningInfo(initialState.userId)
|
|
||||||
.execute {
|
|
||||||
copy(userMXCrossSigningInfo = it.invoke()?.getOrNull())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
session.rx().liveUserCryptoDevices(initialState.userId)
|
||||||
|
.map {
|
||||||
|
Pair(
|
||||||
|
it.fold(true, { prev, dev -> prev && dev.isVerified }),
|
||||||
|
it.fold(true, { prev, dev -> prev && (dev.trustLevel?.crossSigningVerified == true) })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.execute { it ->
|
||||||
|
copy(
|
||||||
|
allDevicesAreTrusted = it()?.first == true,
|
||||||
|
allDevicesAreCrossSignedTrusted = it()?.second == true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
session.rx().liveCrossSigningInfo(initialState.userId)
|
||||||
|
.execute {
|
||||||
|
copy(userMXCrossSigningInfo = it.invoke()?.getOrNull())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeIgnoredState() {
|
private fun observeIgnoredState() {
|
||||||
|
@ -143,7 +155,12 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
|
||||||
if (!state.isMine && state.userMXCrossSigningInfo?.isTrusted() == false) {
|
if (!state.isMine && state.userMXCrossSigningInfo?.isTrusted() == false) {
|
||||||
// ok, let's find or create the DM room
|
// ok, let's find or create the DM room
|
||||||
_actionResultLiveData.postValue(
|
_actionResultLiveData.postValue(
|
||||||
LiveEvent(Success(action.copy(userId = state.userId)))
|
LiveEvent(Success(
|
||||||
|
action.copy(
|
||||||
|
userId = state.userId,
|
||||||
|
canCrossSign = session.getCrossSigningService().canCrossSign()
|
||||||
|
)
|
||||||
|
))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -23,6 +23,7 @@ import im.vector.riotx.core.epoxy.profiles.buildProfileAction
|
||||||
import im.vector.riotx.core.epoxy.profiles.buildProfileSection
|
import im.vector.riotx.core.epoxy.profiles.buildProfileSection
|
||||||
import im.vector.riotx.core.resources.ColorProvider
|
import im.vector.riotx.core.resources.ColorProvider
|
||||||
import im.vector.riotx.core.resources.StringProvider
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
|
import im.vector.riotx.core.ui.list.genericFooterItem
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RoomProfileController @Inject constructor(
|
class RoomProfileController @Inject constructor(
|
||||||
|
@ -55,13 +56,11 @@ class RoomProfileController @Inject constructor(
|
||||||
} else {
|
} else {
|
||||||
R.string.room_profile_not_encrypted_subtitle
|
R.string.room_profile_not_encrypted_subtitle
|
||||||
}
|
}
|
||||||
buildProfileAction(
|
genericFooterItem {
|
||||||
id = "learn_more",
|
id("e2e info")
|
||||||
title = stringProvider.getString(R.string.room_profile_section_security_learn_more),
|
centered(false)
|
||||||
dividerColor = dividerColor,
|
text(stringProvider.getString(learnMoreSubtitle))
|
||||||
subtitle = stringProvider.getString(learnMoreSubtitle),
|
}
|
||||||
action = { callback?.onLearnMoreClicked() }
|
|
||||||
)
|
|
||||||
|
|
||||||
// More
|
// More
|
||||||
buildProfileSection(stringProvider.getString(R.string.room_profile_section_more))
|
buildProfileSection(stringProvider.getString(R.string.room_profile_section_more))
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,7 +143,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
||||||
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_trusted)
|
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_trusted)
|
||||||
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_complete)
|
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_complete)
|
||||||
} else if (xSigningKeysAreTrusted) {
|
} else if (xSigningKeysAreTrusted) {
|
||||||
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_warning)
|
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_custom)
|
||||||
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_trusted)
|
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_trusted)
|
||||||
} else if (xSigningIsEnableInAccount) {
|
} else if (xSigningIsEnableInAccount) {
|
||||||
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_black)
|
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_black)
|
||||||
|
|
|
@ -63,7 +63,7 @@ class CrossSigningEpoxyController @Inject constructor(
|
||||||
} else if (data.xSigningKeysAreTrusted) {
|
} else if (data.xSigningKeysAreTrusted) {
|
||||||
genericItem {
|
genericItem {
|
||||||
id("trusted")
|
id("trusted")
|
||||||
titleIconResourceId(R.drawable.ic_shield_warning)
|
titleIconResourceId(R.drawable.ic_shield_custom)
|
||||||
title(stringProvider.getString(R.string.encryption_information_dg_xsigning_trusted))
|
title(stringProvider.getString(R.string.encryption_information_dg_xsigning_trusted))
|
||||||
}
|
}
|
||||||
if (!data.isUploadingKeys) {
|
if (!data.isUploadingKeys) {
|
||||||
|
|
|
@ -120,6 +120,12 @@ class CrossSigningSettingsFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResetCrossSigningKeys() {
|
override fun onResetCrossSigningKeys() {
|
||||||
viewModel.handle(CrossSigningAction.InitializeCrossSigning())
|
AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle(R.string.dialog_title_confirmation)
|
||||||
|
.setMessage(R.string.are_you_sure)
|
||||||
|
.setPositiveButton(R.string.ok) { _, _ ->
|
||||||
|
viewModel.handle(CrossSigningAction.InitializeCrossSigning())
|
||||||
|
}
|
||||||
|
.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
14
vector/src/main/res/drawable/ic_shield_custom.xml
Normal file
14
vector/src/main/res/drawable/ic_shield_custom.xml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:pathData="M12,21C12,21 21,17.2 21,11.5V4.85L12,2L3,4.85V11.5C3,17.2 12,21 12,21Z"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:fillColor="#03B381"
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:strokeColor="#ffffff"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
|
@ -63,9 +63,11 @@
|
||||||
android:id="@+id/matrixProfileDecorationToolbarAvatarImageView"
|
android:id="@+id/matrixProfileDecorationToolbarAvatarImageView"
|
||||||
android:layout_width="24dp"
|
android:layout_width="24dp"
|
||||||
android:layout_height="24dp"
|
android:layout_height="24dp"
|
||||||
|
android:alpha="0"
|
||||||
app:layout_constraintCircle="@+id/matrixProfileToolbarAvatarImageView"
|
app:layout_constraintCircle="@+id/matrixProfileToolbarAvatarImageView"
|
||||||
app:layout_constraintCircleAngle="135"
|
app:layout_constraintCircleAngle="135"
|
||||||
app:layout_constraintCircleRadius="20dp"
|
app:layout_constraintCircleRadius="20dp"
|
||||||
|
tools:alpha="1"
|
||||||
tools:ignore="MissingConstraints"
|
tools:ignore="MissingConstraints"
|
||||||
tools:src="@drawable/ic_shield_trusted" />
|
tools:src="@drawable/ic_shield_trusted" />
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<im.vector.riotx.core.platform.StateView xmlns:android="http://schemas.android.com/apk/res/android"
|
<im.vector.riotx.core.platform.StateView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/memberProfileStateView"
|
android:id="@+id/memberProfileStateView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -7,68 +8,94 @@
|
||||||
android:background="?riotx_background"
|
android:background="?riotx_background"
|
||||||
android:padding="16dp">
|
android:padding="16dp">
|
||||||
|
|
||||||
<LinearLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/memberProfileInfoContainer"
|
android:id="@+id/memberProfileInfoContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/memberProfileNameView"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/memberProfileNameView">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/memberProfileAvatarView"
|
android:id="@+id/memberProfileAvatarView"
|
||||||
android:layout_width="128dp"
|
android:layout_width="128dp"
|
||||||
android:layout_height="128dp"
|
android:layout_height="128dp"
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:layout_marginBottom="16dp"
|
android:layout_marginBottom="16dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/memberProfileNameView"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
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"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_horizontal"
|
android:gravity="center"
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:textAppearance="@style/Vector.Toolbar.Title"
|
android:textAppearance="@style/Vector.Toolbar.Title"
|
||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/memberProfileIdView"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/memberProfileAvatarView"
|
||||||
tools:text="@sample/matrix.json/data/displayName" />
|
tools:text="@sample/matrix.json/data/displayName" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/memberProfileIdView"
|
android:id="@+id/memberProfileIdView"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:layout_marginBottom="16dp"
|
android:layout_marginBottom="16dp"
|
||||||
|
android:gravity="center"
|
||||||
android:textAppearance="@style/Vector.Toolbar.Title"
|
android:textAppearance="@style/Vector.Toolbar.Title"
|
||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/memberProfilePowerLevelView"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/memberProfileNameView"
|
||||||
tools:text="@sample/matrix.json/data/mxid" />
|
tools:text="@sample/matrix.json/data/mxid" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/memberProfilePowerLevelView"
|
android:id="@+id/memberProfilePowerLevelView"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:layout_marginStart="40dp"
|
|
||||||
android:layout_marginEnd="40dp"
|
|
||||||
android:layout_marginBottom="16dp"
|
android:layout_marginBottom="16dp"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/memberProfileStatusView"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/memberProfileIdView"
|
||||||
tools:text="Admin in Matrix" />
|
tools:text="Admin in Matrix" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/memberProfileStatusView"
|
android:id="@+id/memberProfileStatusView"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:layout_marginStart="40dp"
|
|
||||||
android:layout_marginEnd="40dp"
|
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/memberProfilePowerLevelView"
|
||||||
tools:text="Here is a profile status"
|
tools:text="Here is a profile status"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
</LinearLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
</im.vector.riotx.core.platform.StateView>
|
</im.vector.riotx.core.platform.StateView>
|
|
@ -91,6 +91,8 @@
|
||||||
|
|
||||||
<string name="unignore">Unignore</string>
|
<string name="unignore">Unignore</string>
|
||||||
|
|
||||||
|
<string name="verify_cannot_cross_sign">This session is unable to share this verification with your other sessions.\nThe verification will be saved locally and shared in a future version of the app.</string>
|
||||||
|
|
||||||
<string name="room_list_sharing_header_recent_rooms">Recent rooms</string>
|
<string name="room_list_sharing_header_recent_rooms">Recent rooms</string>
|
||||||
<string name="room_list_sharing_header_other_rooms">Other rooms</string>
|
<string name="room_list_sharing_header_other_rooms">Other rooms</string>
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue