Merge pull request #923 from vector-im/feature/xcrossing_fix

Decoration in room profile and improve Rx flow
This commit is contained in:
Benoit Marty 2020-01-31 20:48:20 +01:00 committed by GitHub
commit 2616a889ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 232 additions and 38 deletions

View File

@ -16,6 +16,8 @@
package im.vector.matrix.rx package im.vector.matrix.rx
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
@ -30,12 +32,58 @@ 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 io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.Single import io.reactivex.Single
import io.reactivex.functions.BiFunction
class RxRoom(private val room: Room) { class RxRoom(private val room: Room, private val session: Session) {
fun liveRoomSummary(): Observable<Optional<RoomSummary>> { fun liveRoomSummary(): Observable<Optional<RoomSummary>> {
return room.getRoomSummaryLive().asObservable() val summaryObservable = room.getRoomSummaryLive()
.asObservable()
.startWith(room.roomSummary().toOptional()) .startWith(room.roomSummary().toOptional())
val memberIdsChangeObservable = summaryObservable
.map {
it.getOrNull()?.let { roomSummary ->
if (roomSummary.isEncrypted) {
// Return the list of other users
roomSummary.otherMemberIds
} else {
// Return an empty list, the room is not encrypted
emptyList()
}
}.orEmpty()
}.distinctUntilChanged()
// Observe the device info of the users in the room
val cryptoDeviceInfoObservable = memberIdsChangeObservable
.switchMap { otherUserIds ->
session.getLiveCryptoDeviceInfo(otherUserIds)
.asObservable()
.map {
// If any key change, emit the userIds list
otherUserIds
}
}
val roomEncryptionTrustLevelObservable = cryptoDeviceInfoObservable
.map { otherUserIds ->
if (otherUserIds.isEmpty()) {
Optional<RoomEncryptionTrustLevel>(null)
} else {
session.getCrossSigningService().getTrustLevelForUsers(otherUserIds).toOptional()
}
}
return Observable
.combineLatest<Optional<RoomSummary>, Optional<RoomEncryptionTrustLevel>, Optional<RoomSummary>>(
summaryObservable,
roomEncryptionTrustLevelObservable,
BiFunction { summary, level ->
summary.getOrNull()?.copy(
roomEncryptionTrustLevel = level.getOrNull()
).toOptional()
}
)
} }
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMemberSummary>> { fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMemberSummary>> {
@ -88,6 +136,6 @@ class RxRoom(private val room: Room) {
} }
} }
fun Room.rx(): RxRoom { fun Room.rx(session: Session): RxRoom {
return RxRoom(this) return RxRoom(this, session)
} }

View File

@ -33,12 +33,32 @@ import im.vector.matrix.android.api.util.toOptional
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo 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
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>> {
return session.getRoomSummariesLive(queryParams).asObservable() val summariesObservable = session.getRoomSummariesLive(queryParams).asObservable()
.startWith(session.getRoomSummaries(queryParams)) .startWith(session.getRoomSummaries(queryParams))
val cryptoDeviceInfoObservable = session.getLiveCryptoDeviceInfo().asObservable()
return Observable
.combineLatest<List<RoomSummary>, List<CryptoDeviceInfo>, List<RoomSummary>>(
summariesObservable,
cryptoDeviceInfoObservable,
BiFunction { summaries, _ ->
summaries.map {
if (it.isEncrypted) {
it.copy(
roomEncryptionTrustLevel = session.getCrossSigningService().getTrustLevelForUsers(it.otherMemberIds)
)
} else {
it
}
}
}
)
} }
fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Observable<List<GroupSummary>> { fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Observable<List<GroupSummary>> {

View File

@ -120,8 +120,12 @@ interface CryptoService {
fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo>
fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>>
fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>> fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>>
fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>>
fun addNewSessionListener(newSessionListener: NewSessionListener) fun addNewSessionListener(newSessionListener: NewSessionListener)
fun removeSessionListener(listener: NewSessionListener) fun removeSessionListener(listener: NewSessionListener)

View File

@ -18,6 +18,7 @@ package im.vector.matrix.android.api.session.crypto.crosssigning
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustResult import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustResult
import im.vector.matrix.android.internal.crypto.crosssigning.UserTrustResult import im.vector.matrix.android.internal.crypto.crosssigning.UserTrustResult
@ -62,4 +63,6 @@ interface CrossSigningService {
fun checkDeviceTrust(otherUserId: String, fun checkDeviceTrust(otherUserId: String,
otherDeviceId: String, otherDeviceId: String,
locallyTrusted: Boolean?): DeviceTrustResult locallyTrusted: Boolean?): DeviceTrustResult
fun getTrustLevelForUsers(userIds: List<String>): RoomEncryptionTrustLevel
} }

View File

@ -403,10 +403,18 @@ internal class DefaultCryptoService @Inject constructor(
return cryptoStore.getUserDevices(userId)?.map { it.value } ?: emptyList() return cryptoStore.getUserDevices(userId)?.map { it.value } ?: emptyList()
} }
override fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>> {
return cryptoStore.getLiveDeviceList()
}
override fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>> { override fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>> {
return cryptoStore.getLiveDeviceList(userId) return cryptoStore.getLiveDeviceList(userId)
} }
override fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>> {
return cryptoStore.getLiveDeviceList(userIds)
}
/** /**
* Set the devices as known * Set the devices as known
* *

View File

@ -19,6 +19,8 @@ package im.vector.matrix.android.internal.crypto.crosssigning
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import dagger.Lazy import dagger.Lazy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
import im.vector.matrix.android.api.extensions.orFalse
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.Optional
@ -655,4 +657,35 @@ internal class DefaultCrossSigningService @Inject constructor(
} }
} }
} }
override fun getTrustLevelForUsers(userIds: List<String>): RoomEncryptionTrustLevel {
val atLeastOneTrusted = userIds
.filter { it != userId }
.map { getUserCrossSigningKeys(it) }
.any { it?.isTrusted() == true }
return if (!atLeastOneTrusted) {
RoomEncryptionTrustLevel.Default
} else {
// I have verified at least one other user
val allDevices = userIds.mapNotNull {
cryptoStore.getUserDeviceList(it)
}.flatten()
if (getMyCrossSigningKeys() != null) {
val hasWarning = allDevices.any { !it.trustLevel?.crossSigningVerified.orFalse() }
if (hasWarning) {
RoomEncryptionTrustLevel.Warning
} else {
RoomEncryptionTrustLevel.Trusted
}
} else {
val hasWarningLegacy = allDevices.any { !it.isVerified }
if (hasWarningLegacy) {
RoomEncryptionTrustLevel.Warning
} else {
RoomEncryptionTrustLevel.Trusted
}
}
}
}
} }

View File

@ -200,6 +200,11 @@ internal interface IMXCryptoStore {
fun getUserDeviceList(userId: String): List<CryptoDeviceInfo>? fun getUserDeviceList(userId: String): List<CryptoDeviceInfo>?
fun getLiveDeviceList(userId: String): LiveData<List<CryptoDeviceInfo>> fun getLiveDeviceList(userId: String): LiveData<List<CryptoDeviceInfo>>
fun getLiveDeviceList(userIds: List<String>): LiveData<List<CryptoDeviceInfo>>
// TODO temp
fun getLiveDeviceList(): LiveData<List<CryptoDeviceInfo>>
/** /**
* Store the crypto algorithm for a room. * Store the crypto algorithm for a room.
* *

View File

@ -398,6 +398,36 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
} }
} }
override fun getLiveDeviceList(userIds: List<String>): LiveData<List<CryptoDeviceInfo>> {
val liveData = monarchy.findAllMappedWithChanges(
{ realm: Realm ->
realm
.where<UserEntity>()
.`in`(UserEntityFields.USER_ID, userIds.toTypedArray())
},
{ entity ->
entity.devices.map { CryptoMapper.mapToModel(it) }
}
)
return Transformations.map(liveData) {
it.firstOrNull() ?: emptyList()
}
}
override fun getLiveDeviceList(): LiveData<List<CryptoDeviceInfo>> {
val liveData = monarchy.findAllMappedWithChanges(
{ realm: Realm ->
realm.where<UserEntity>()
},
{ entity ->
entity.devices.map { CryptoMapper.mapToModel(it) }
}
)
return Transformations.map(liveData) {
it.firstOrNull() ?: emptyList()
}
}
override fun storeRoomAlgorithm(roomId: String, algorithm: String) { override fun storeRoomAlgorithm(roomId: String, algorithm: String) {
doRealmTransaction(realmConfiguration) { doRealmTransaction(realmConfiguration) {
CryptoRoomEntity.getOrCreate(it, roomId).algorithm = algorithm CryptoRoomEntity.getOrCreate(it, roomId).algorithm = algorithm

View File

@ -27,7 +27,8 @@ abstract class AppBarStateChangeListener : OnOffsetChangedListener {
EXPANDED, COLLAPSED, IDLE EXPANDED, COLLAPSED, IDLE
} }
private var currentState = State.IDLE var currentState = State.IDLE
private set
override fun onOffsetChanged(appBarLayout: AppBarLayout, i: Int) { override fun onOffsetChanged(appBarLayout: AppBarLayout, i: Int) {
currentState = if (i == 0) { currentState = if (i == 0) {

View File

@ -152,7 +152,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
observeDrafts() observeDrafts()
observeUnreadState() observeUnreadState()
room.getRoomSummaryLive() room.getRoomSummaryLive()
room.rx().loadRoomMembersIfNeeded().subscribeLogError().disposeOnClear() room.rx(session).loadRoomMembersIfNeeded().subscribeLogError().disposeOnClear()
// Inform the SDK that the room is displayed // Inform the SDK that the room is displayed
session.onRoomDisplayed(initialState.roomId) session.onRoomDisplayed(initialState.roomId)
} }
@ -233,7 +233,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
} }
private fun observeDrafts() { private fun observeDrafts() {
room.rx().liveDrafts() room.rx(session).liveDrafts()
.subscribe { .subscribe {
Timber.d("Draft update --> SetState") Timber.d("Draft update --> SetState")
setState { setState {
@ -874,7 +874,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
} }
private fun observeRoomSummary() { private fun observeRoomSummary() {
room.rx().liveRoomSummary() room.rx(session).liveRoomSummary()
.unwrap() .unwrap()
.execute { async -> .execute { async ->
val typingRoomMembers = val typingRoomMembers =
@ -892,7 +892,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
Observable Observable
.combineLatest<List<TimelineEvent>, RoomSummary, UnreadState>( .combineLatest<List<TimelineEvent>, RoomSummary, UnreadState>(
timelineEvents.observeOn(Schedulers.computation()), timelineEvents.observeOn(Schedulers.computation()),
room.rx().liveRoomSummary().unwrap(), room.rx(session).liveRoomSummary().unwrap(),
BiFunction { timelineEvents, roomSummary -> BiFunction { timelineEvents, roomSummary ->
computeUnreadState(timelineEvents, roomSummary) computeUnreadState(timelineEvents, roomSummary)
} }

View File

@ -134,7 +134,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
private fun observeEvent() { private fun observeEvent() {
if (room == null) return if (room == null) return
room.rx() room.rx(session)
.liveTimelineEvent(eventId) .liveTimelineEvent(eventId)
.unwrap() .unwrap()
.execute { .execute {
@ -144,7 +144,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
private fun observeReactions() { private fun observeReactions() {
if (room == null) return if (room == null) return
room.rx() room.rx(session)
.liveAnnotationSummary(eventId) .liveAnnotationSummary(eventId)
.map { annotations -> .map { annotations ->
EmojiDataSource.quickEmojis.map { emoji -> EmojiDataSource.quickEmojis.map { emoji ->

View File

@ -86,7 +86,7 @@ class ViewReactionsViewModel @AssistedInject constructor(@Assisted
} }
private fun observeEventAnnotationSummaries() { private fun observeEventAnnotationSummaries() {
RxRoom(room) RxRoom(room, session)
.liveAnnotationSummary(eventId) .liveAnnotationSummary(eventId)
.unwrap() .unwrap()
.flatMapSingle { summaries -> .flatMapSingle { summaries ->

View File

@ -28,7 +28,7 @@ import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
class RoomListQuickActionsViewModel @AssistedInject constructor(@Assisted initialState: RoomListQuickActionsState, class RoomListQuickActionsViewModel @AssistedInject constructor(@Assisted initialState: RoomListQuickActionsState,
session: Session private val session: Session
) : VectorViewModel<RoomListQuickActionsState, EmptyAction, EmptyViewEvents>(initialState) { ) : VectorViewModel<RoomListQuickActionsState, EmptyAction, EmptyViewEvents>(initialState) {
@AssistedInject.Factory @AssistedInject.Factory
@ -54,7 +54,7 @@ class RoomListQuickActionsViewModel @AssistedInject constructor(@Assisted initia
private fun observeNotificationState() { private fun observeNotificationState() {
room room
.rx() .rx(session)
.liveNotificationState() .liveNotificationState()
.execute { .execute {
copy(roomNotificationState = it) copy(roomNotificationState = it)
@ -63,7 +63,7 @@ class RoomListQuickActionsViewModel @AssistedInject constructor(@Assisted initia
private fun observeRoomSummary() { private fun observeRoomSummary() {
room room
.rx() .rx(session)
.liveRoomSummary() .liveRoomSummary()
.unwrap() .unwrap()
.execute { .execute {

View File

@ -153,7 +153,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
val queryParams = roomMemberQueryParams { val queryParams = roomMemberQueryParams {
this.userId = QueryStringValue.Equals(initialState.userId, QueryStringValue.Case.SENSITIVE) this.userId = QueryStringValue.Equals(initialState.userId, QueryStringValue.Case.SENSITIVE)
} }
room.rx().liveRoomMembers(queryParams) room.rx(session).liveRoomMembers(queryParams)
.map { it.firstOrNull()?.toMatrixItem().toOptional() } .map { it.firstOrNull()?.toMatrixItem().toOptional() }
.unwrap() .unwrap()
.execute { .execute {
@ -176,8 +176,8 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
} }
private fun observeRoomSummaryAndPowerLevels(room: Room) { private fun observeRoomSummaryAndPowerLevels(room: Room) {
val roomSummaryLive = room.rx().liveRoomSummary().unwrap() val roomSummaryLive = room.rx(session).liveRoomSummary().unwrap()
val powerLevelsContentLive = room.rx().liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS) val powerLevelsContentLive = room.rx(session).liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS)
.mapOptional { it.content.toModel<PowerLevelsContent>() } .mapOptional { it.content.toModel<PowerLevelsContent>() }
.unwrap() .unwrap()

View File

@ -21,6 +21,7 @@ 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.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import com.airbnb.mvrx.args import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
@ -34,6 +35,7 @@ import im.vector.riotx.core.extensions.configureWith
import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.extensions.exhaustive
import im.vector.riotx.core.extensions.setTextOrHide import im.vector.riotx.core.extensions.setTextOrHide
import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.features.crypto.util.toImageRes
import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.list.actions.RoomListActionsArgs import im.vector.riotx.features.home.room.list.actions.RoomListActionsArgs
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsBottomSheet import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsBottomSheet
@ -75,8 +77,12 @@ class RoomProfileFragment @Inject constructor(
} }
setupToolbar(matrixProfileToolbar) setupToolbar(matrixProfileToolbar)
setupRecyclerView() setupRecyclerView()
appBarStateChangeListener = MatrixItemAppBarStateChangeListener(headerView, listOf(matrixProfileToolbarAvatarImageView, appBarStateChangeListener = MatrixItemAppBarStateChangeListener(
matrixProfileToolbarTitleView)) headerView,
listOf(matrixProfileToolbarAvatarImageView,
matrixProfileToolbarTitleView,
matrixProfileDecorationToolbarAvatarImageView)
)
matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener) matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener)
roomProfileViewModel.observeViewEvents { roomProfileViewModel.observeViewEvents {
when (it) { when (it) {
@ -139,6 +145,9 @@ class RoomProfileFragment @Inject constructor(
val matrixItem = it.toMatrixItem() val matrixItem = it.toMatrixItem()
avatarRenderer.render(matrixItem, roomProfileAvatarView) avatarRenderer.render(matrixItem, roomProfileAvatarView)
avatarRenderer.render(matrixItem, matrixProfileToolbarAvatarImageView) avatarRenderer.render(matrixItem, matrixProfileToolbarAvatarImageView)
roomProfileDecorationImageView.isVisible = it.roomEncryptionTrustLevel != null
roomProfileDecorationImageView.setImageResource(it.roomEncryptionTrustLevel.toImageRes())
matrixProfileDecorationToolbarAvatarImageView.setImageResource(it.roomEncryptionTrustLevel.toImageRes())
} }
} }
roomProfileController.setData(state) roomProfileController.setData(state)

View File

@ -56,7 +56,7 @@ class RoomProfileViewModel @AssistedInject constructor(@Assisted initialState: R
} }
private fun observeRoomSummary() { private fun observeRoomSummary() {
room.rx().liveRoomSummary() room.rx(session).liveRoomSummary()
.unwrap() .unwrap()
.execute { .execute {
copy(roomSummary = it) copy(roomSummary = it)

View File

@ -72,8 +72,8 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
} }
Observable Observable
.combineLatest<List<RoomMemberSummary>, PowerLevelsContent, RoomMemberSummaries>( .combineLatest<List<RoomMemberSummary>, PowerLevelsContent, RoomMemberSummaries>(
room.rx().liveRoomMembers(roomMemberQueryParams), room.rx(session).liveRoomMembers(roomMemberQueryParams),
room.rx() room.rx(session)
.liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS) .liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS)
.mapOptional { it.content.toModel<PowerLevelsContent>() } .mapOptional { it.content.toModel<PowerLevelsContent>() }
.unwrap(), .unwrap(),
@ -87,7 +87,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
} }
private fun observeRoomSummary() { private fun observeRoomSummary() {
room.rx().liveRoomSummary() room.rx(session).liveRoomSummary()
.unwrap() .unwrap()
.execute { async -> .execute { async ->
copy(roomSummary = async) copy(roomSummary = async)

View File

@ -53,7 +53,7 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState:
} }
private fun observeRoomSummary() { private fun observeRoomSummary() {
room.rx().liveRoomSummary() room.rx(session).liveRoomSummary()
.unwrap() .unwrap()
.execute { async -> .execute { async ->
copy(roomSummary = async) copy(roomSummary = async)

View File

@ -20,8 +20,8 @@
android:layout_height="match_parent" android:layout_height="match_parent"
app:contentScrim="?riotx_background" app:contentScrim="?riotx_background"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap" app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
app:scrimVisibleHeightTrigger="80dp"
app:scrimAnimationDuration="250" app:scrimAnimationDuration="250"
app:scrimVisibleHeightTrigger="80dp"
app:titleEnabled="false" app:titleEnabled="false"
app:toolbarId="@+id/matrixProfileToolbar"> app:toolbarId="@+id/matrixProfileToolbar">
@ -59,6 +59,16 @@
tools:alpha="1" tools:alpha="1"
tools:src="@tools:sample/avatars" /> tools:src="@tools:sample/avatars" />
<ImageView
android:id="@+id/matrixProfileDecorationToolbarAvatarImageView"
android:layout_width="24dp"
android:layout_height="24dp"
app:layout_constraintCircle="@+id/matrixProfileToolbarAvatarImageView"
app:layout_constraintCircleAngle="135"
app:layout_constraintCircleRadius="20dp"
tools:ignore="MissingConstraints"
tools:src="@drawable/ic_shield_trusted" />
<TextView <TextView
android:id="@+id/matrixProfileToolbarTitleView" android:id="@+id/matrixProfileToolbarTitleView"
android:layout_width="0dp" android:layout_width="0dp"

View File

@ -1,49 +1,68 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?riotx_background" android:background="?riotx_background"
android:orientation="vertical"
android:padding="16dp"> android:padding="16dp">
<ImageView <ImageView
android:id="@+id/roomProfileAvatarView" android:id="@+id/roomProfileAvatarView"
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/roomProfileNameView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" /> tools:src="@tools:sample/avatars" />
<ImageView
android:id="@+id/roomProfileDecorationImageView"
android:layout_width="48dp"
android:layout_height="48dp"
app:layout_constraintCircle="@+id/roomProfileAvatarView"
app:layout_constraintCircleAngle="135"
app:layout_constraintCircleRadius="64dp"
tools:ignore="MissingConstraints"
tools:src="@drawable/ic_shield_trusted" />
<TextView <TextView
android:id="@+id/roomProfileNameView" android:id="@+id/roomProfileNameView"
android:layout_width="wrap_content" android:layout_width="0dp"
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/roomProfileAliasView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/roomProfileAvatarView"
tools:text="@sample/matrix.json/data/roomName" /> tools:text="@sample/matrix.json/data/roomName" />
<TextView <TextView
android:id="@+id/roomProfileAliasView" android:id="@+id/roomProfileAliasView"
android:layout_width="wrap_content" android:layout_width="0dp"
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:singleLine="true" android:singleLine="true"
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/roomProfileTopicView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/roomProfileNameView"
tools:text="@sample/matrix.json/data/roomAlias" /> tools:text="@sample/matrix.json/data/roomAlias" />
<TextView <TextView
android:id="@+id/roomProfileTopicView" android:id="@+id/roomProfileTopicView"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginStart="40dp" android:layout_marginStart="40dp"
android:layout_marginEnd="40dp" android:layout_marginEnd="40dp"
android:autoLink="web" android:autoLink="web"
@ -51,6 +70,10 @@
android:gravity="center" android:gravity="center"
android:textSize="14sp" android:textSize="14sp"
android:textStyle="normal" android:textStyle="normal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/roomProfileAliasView"
tools:text="@sample/matrix.json/data/roomTopic" /> tools:text="@sample/matrix.json/data/roomTopic" />
</LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>