Profile: handle ignore/unignore action + adjust UI

This commit is contained in:
ganfra 2020-01-14 17:08:21 +01:00
parent 162f0949fa
commit df4df81ef3
20 changed files with 352 additions and 247 deletions

View File

@ -20,16 +20,18 @@ package im.vector.riotx.core.animations
import android.view.View import android.view.View
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout
class MatrixItemAppBarStateChangeListener(private val animationDuration: Long, private val views: List<View>) : AppBarStateChangeListener() { class MatrixItemAppBarStateChangeListener(private val headerView: View, private val toolbarViews: List<View>) : AppBarStateChangeListener() {
override fun onStateChanged(appBarLayout: AppBarLayout, state: State) { override fun onStateChanged(appBarLayout: AppBarLayout, state: State) {
if (state == State.COLLAPSED) { if (state == State.COLLAPSED) {
views.forEach { headerView.visibility = View.INVISIBLE
it.animate().alpha(1f).duration = animationDuration + 100 toolbarViews.forEach {
it.animate().alpha(1f).duration = 150
} }
} else { } else {
views.forEach { headerView.visibility = View.VISIBLE
it.animate().alpha(0f).duration = animationDuration - 100 toolbarViews.forEach {
it.animate().alpha(0f).duration = 150
} }
} }
} }

View File

@ -34,7 +34,7 @@ class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
data class Error(val message: CharSequence? = null) : State() data class Error(val message: CharSequence? = null) : State()
} }
private var eventCallback: EventCallback? = null var eventCallback: EventCallback? = null
var contentView: View? = null var contentView: View? = null

View File

@ -14,8 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
@file:Suppress("DEPRECATION")
package im.vector.riotx.core.platform package im.vector.riotx.core.platform
import android.app.ProgressDialog
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
@ -59,6 +62,9 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
protected lateinit var navigator: Navigator protected lateinit var navigator: Navigator
protected lateinit var errorFormatter: ErrorFormatter protected lateinit var errorFormatter: ErrorFormatter
private var progress: ProgressDialog? = null
/* ========================================================================================== /* ==========================================================================================
* View model * View model
* ========================================================================================== */ * ========================================================================================== */
@ -177,6 +183,19 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
} }
} }
protected fun showLoadingDialog(message: CharSequence, cancelable: Boolean = false) {
progress = ProgressDialog(requireContext()).apply {
setCancelable(cancelable)
setMessage(message)
setProgressStyle(ProgressDialog.STYLE_SPINNER)
show()
}
}
protected fun dismissLoadingDialog(){
progress?.dismiss()
}
/* ========================================================================================== /* ==========================================================================================
* Toolbar * Toolbar
* ========================================================================================== */ * ========================================================================================== */

View File

@ -19,6 +19,7 @@ package im.vector.riotx.core.utils
import com.jakewharton.rxrelay2.BehaviorRelay import com.jakewharton.rxrelay2.BehaviorRelay
import com.jakewharton.rxrelay2.PublishRelay import com.jakewharton.rxrelay2.PublishRelay
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
interface DataSource<T> { interface DataSource<T> {
@ -37,7 +38,7 @@ open class BehaviorDataSource<T>(private val defaultValue: T? = null) : MutableD
private val behaviorRelay = createRelay() private val behaviorRelay = createRelay()
override fun observe(): Observable<T> { override fun observe(): Observable<T> {
return behaviorRelay.hide().observeOn(Schedulers.computation()) return behaviorRelay.hide().observeOn(AndroidSchedulers.mainThread())
} }
override fun post(value: T) { override fun post(value: T) {
@ -61,7 +62,7 @@ open class PublishDataSource<T> : MutableDataSource<T> {
private val publishRelay = PublishRelay.create<T>() private val publishRelay = PublishRelay.create<T>()
override fun observe(): Observable<T> { override fun observe(): Observable<T> {
return publishRelay.hide() return publishRelay.hide().observeOn(AndroidSchedulers.mainThread())
} }
override fun post(value: T) { override fun post(value: T) {

View File

@ -21,11 +21,7 @@ import im.vector.riotx.core.platform.VectorViewModelAction
sealed class RoomMemberProfileAction : VectorViewModelAction { sealed class RoomMemberProfileAction : VectorViewModelAction {
sealed class Displayable : RoomMemberProfileAction() { object RetryFetchingInfo: RoomMemberProfileAction()
object JumpToReadReceipt : Displayable() object IgnoreUser: RoomMemberProfileAction()
object Ignore : Displayable()
object Mention : Displayable()
}
} }

View File

@ -37,36 +37,33 @@ class RoomMemberProfileController @Inject constructor(private val stringProvider
} }
override fun buildModels(data: RoomMemberProfileViewState?) { override fun buildModels(data: RoomMemberProfileViewState?) {
if (data == null) { if (data?.userMatrixItem?.invoke() == null) {
return return
} }
val roomMemberSummary = data.roomMemberSummary() if (data.showAsMember) {
val profileInfo = data.profileInfo()
if (roomMemberSummary == null && profileInfo != null) {
buildUserActions()
} else if (roomMemberSummary != null) {
buildRoomMemberActions(data) buildRoomMemberActions(data)
} else {
buildUserActions(data)
} }
} }
private fun buildUserActions() { private fun buildUserActions(state: RoomMemberProfileViewState) {
val ignoreActionTitle = state.buildIgnoreActionTitle() ?: return
// More // More
buildProfileSection(stringProvider.getString(R.string.room_profile_section_more)) buildProfileSection(stringProvider.getString(R.string.room_profile_section_more))
buildProfileAction( buildProfileAction(
id = "ignore", id = "ignore",
title = stringProvider.getString(R.string.ignore), title = ignoreActionTitle,
destructive = true, destructive = true,
editable = false, editable = false,
action = { callback?.onIgnoreClicked() } action = { callback?.onIgnoreClicked() }
) )
} }
private fun buildRoomMemberActions(data: RoomMemberProfileViewState) { private fun buildRoomMemberActions(state: RoomMemberProfileViewState) {
val roomSummaryEntity = data.roomSummary() ?: return
// Security // Security
buildProfileSection(stringProvider.getString(R.string.room_profile_section_security)) buildProfileSection(stringProvider.getString(R.string.room_profile_section_security))
val learnMoreSubtitle = if (roomSummaryEntity.isEncrypted) { val learnMoreSubtitle = if (state.isRoomEncrypted) {
R.string.room_profile_encrypted_subtitle R.string.room_profile_encrypted_subtitle
} else { } else {
R.string.room_profile_not_encrypted_subtitle R.string.room_profile_not_encrypted_subtitle
@ -80,7 +77,7 @@ class RoomMemberProfileController @Inject constructor(private val stringProvider
) )
// More // More
if (!data.isMine) { if (!state.isMine) {
buildProfileSection(stringProvider.getString(R.string.room_profile_section_more)) buildProfileSection(stringProvider.getString(R.string.room_profile_section_more))
buildProfileAction( buildProfileAction(
id = "read_receipt", id = "read_receipt",
@ -94,15 +91,26 @@ class RoomMemberProfileController @Inject constructor(private val stringProvider
editable = false, editable = false,
action = { callback?.onMentionClicked() } action = { callback?.onMentionClicked() }
) )
buildProfileAction( val ignoreActionTitle = state.buildIgnoreActionTitle()
id = "ignore", if (ignoreActionTitle != null) {
title = stringProvider.getString(R.string.ignore), buildProfileAction(
destructive = true, id = "ignore",
editable = false, title = ignoreActionTitle,
action = { callback?.onIgnoreClicked() } destructive = true,
) editable = false,
action = { callback?.onIgnoreClicked() }
)
}
} }
}
private fun RoomMemberProfileViewState.buildIgnoreActionTitle(): String? {
val isIgnored = isIgnored() ?: return null
return if (isIgnored) {
stringProvider.getString(R.string.unignore)
} else {
stringProvider.getString(R.string.ignore)
}
} }

View File

@ -15,28 +15,26 @@
* *
*/ */
@file:Suppress("DEPRECATION")
package im.vector.riotx.features.roommemberprofile 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 com.airbnb.mvrx.args import com.airbnb.mvrx.*
import com.airbnb.mvrx.fragmentViewModel import im.vector.matrix.android.api.util.MatrixItem
import com.airbnb.mvrx.withState
import im.vector.matrix.android.api.session.room.powerlevers.PowerLevelsConstants
import im.vector.matrix.android.api.session.room.powerlevers.PowerLevelsHelper
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.animations.AppBarStateChangeListener import im.vector.riotx.core.animations.AppBarStateChangeListener
import im.vector.riotx.core.animations.MatrixItemAppBarStateChangeListener import im.vector.riotx.core.animations.MatrixItemAppBarStateChangeListener
import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.cleanup
import im.vector.riotx.core.extensions.configureWith import im.vector.riotx.core.extensions.configureWith
import im.vector.riotx.core.extensions.setTextOrHide import im.vector.riotx.core.extensions.setTextOrHide
import im.vector.riotx.core.platform.StateView
import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.AvatarRenderer
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_matrix_profile.* import kotlinx.android.synthetic.main.fragment_matrix_profile.*
import kotlinx.android.synthetic.main.fragment_matrix_profile.matrixProfileHeaderView
import kotlinx.android.synthetic.main.view_stub_room_member_profile_header.* import kotlinx.android.synthetic.main.view_stub_room_member_profile_header.*
import javax.inject.Inject import javax.inject.Inject
@ -62,14 +60,32 @@ class RoomMemberProfileFragment @Inject constructor(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupToolbar(matrixProfileToolbar) setupToolbar(matrixProfileToolbar)
matrixProfileHeaderView.apply { val headerView = matrixProfileHeaderView.let {
layoutResource = R.layout.view_stub_room_member_profile_header it.layoutResource = R.layout.view_stub_room_member_profile_header
inflate() it.inflate()
} }
memberProfileStateView.eventCallback = object : StateView.EventCallback {
override fun onRetryClicked() {
viewModel.handle(RoomMemberProfileAction.RetryFetchingInfo)
}
}
memberProfileStateView.contentView = memberProfileInfoContainer
matrixProfileRecyclerView.configureWith(roomMemberProfileController, hasFixedSize = true) matrixProfileRecyclerView.configureWith(roomMemberProfileController, hasFixedSize = true)
roomMemberProfileController.callback = this roomMemberProfileController.callback = this
appBarStateChangeListener = MatrixItemAppBarStateChangeListener(matrixProfileCollapsingToolbarLayout.scrimAnimationDuration, listOf(matrixProfileToolbarAvatarImageView, matrixProfileToolbarTitleView)) appBarStateChangeListener = MatrixItemAppBarStateChangeListener(headerView, listOf(matrixProfileToolbarAvatarImageView,
matrixProfileToolbarTitleView))
matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener) matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener)
viewModel.viewEvents
.observe()
.subscribe {
dismissLoadingDialog()
when (it) {
is RoomMemberProfileViewEvents.Loading -> showLoadingDialog(it.message)
is RoomMemberProfileViewEvents.Failure -> showErrorInSnackbar(it.throwable)
}
}
.disposeOnDestroyView()
} }
override fun onDestroyView() { override fun onDestroyView() {
@ -81,42 +97,37 @@ class RoomMemberProfileFragment @Inject constructor(
override fun invalidate() = withState(viewModel) { state -> override fun invalidate() = withState(viewModel) { state ->
val memberMatrixItem = state.memberAsMatrixItem() when (val asyncUserMatrixItem = state.userMatrixItem) {
if (memberMatrixItem != null) { is Incomplete -> {
memberProfileIdView.text = memberMatrixItem.id matrixProfileToolbarTitleView.text = state.userId
val bestName = memberMatrixItem.getBestName() avatarRenderer.render(MatrixItem.UserItem(state.userId, null, null), matrixProfileToolbarAvatarImageView)
memberProfileNameView.text = bestName memberProfileStateView.state = StateView.State.Loading
matrixProfileToolbarTitleView.text = bestName }
avatarRenderer.render(memberMatrixItem, memberProfileAvatarView) is Fail -> {
avatarRenderer.render(memberMatrixItem, matrixProfileToolbarAvatarImageView) avatarRenderer.render(MatrixItem.UserItem(state.userId, null, null), matrixProfileToolbarAvatarImageView)
} matrixProfileToolbarTitleView.text = state.userId
val failureMessage = errorFormatter.toHumanReadable(asyncUserMatrixItem.error)
val roomSummary = state.roomSummary() memberProfileStateView.state = StateView.State.Error(failureMessage)
val powerLevelsContent = state.powerLevelsContent() }
if (powerLevelsContent == null || roomSummary == null) { is Success -> {
memberProfilePowerLevelView.visibility = View.GONE val userMatrixItem = asyncUserMatrixItem()
} else { memberProfileStateView.state = StateView.State.Content
val roomName = roomSummary.toMatrixItem().getBestName() memberProfileIdView.text = userMatrixItem.id
val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent) val bestName = userMatrixItem.getBestName()
val userPowerLevel = powerLevelsHelper.getUserPowerLevel(state.userId) memberProfileNameView.text = bestName
val powerLevelText = if (userPowerLevel == PowerLevelsConstants.DEFAULT_ROOM_ADMIN_LEVEL) { matrixProfileToolbarTitleView.text = bestName
getString(R.string.room_member_power_level_admin_in, roomName) avatarRenderer.render(userMatrixItem, memberProfileAvatarView)
} else if (userPowerLevel == PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL) { avatarRenderer.render(userMatrixItem, matrixProfileToolbarAvatarImageView)
getString(R.string.room_member_power_level_moderator_in, roomName)
} else if (userPowerLevel == PowerLevelsConstants.DEFAULT_ROOM_USER_LEVEL) {
null
} else {
getString(R.string.room_member_power_level_custom_in, userPowerLevel, roomName)
} }
memberProfilePowerLevelView.setTextOrHide(powerLevelText)
} }
memberProfilePowerLevelView.setTextOrHide(state.userPowerLevelString())
roomMemberProfileController.setData(state) roomMemberProfileController.setData(state)
} }
// RoomMemberProfileController.Callback // RoomMemberProfileController.Callback
override fun onIgnoreClicked() { override fun onIgnoreClicked() {
vectorBaseActivity.notImplemented("Ignore") viewModel.handle(RoomMemberProfileAction.IgnoreUser)
} }
override fun onLearnMoreClicked() { override fun onLearnMoreClicked() {

View File

@ -0,0 +1,27 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.roommemberprofile
/**
* Transient events for RoomProfile
*/
sealed class RoomMemberProfileViewEvents {
data class Loading(val message: CharSequence) : RoomMemberProfileViewEvents()
object OnIgnoreActionSuccess : RoomMemberProfileViewEvents()
data class Failure(val throwable: Throwable) : RoomMemberProfileViewEvents()
}

View File

@ -17,25 +17,43 @@
package im.vector.riotx.features.roommemberprofile package im.vector.riotx.features.roommemberprofile
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
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
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.query.QueryStringValue import im.vector.matrix.android.api.query.QueryStringValue
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.profile.ProfileService
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
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.powerlevers.PowerLevelsConstants
import im.vector.matrix.android.api.session.room.powerlevers.PowerLevelsHelper
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.matrix.android.api.util.toOptional import im.vector.matrix.android.api.util.toOptional
import im.vector.matrix.rx.mapOptional import im.vector.matrix.rx.mapOptional
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
import im.vector.matrix.rx.unwrap import im.vector.matrix.rx.unwrap
import im.vector.riotx.R
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import timber.log.Timber import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.utils.DataSource
import im.vector.riotx.core.utils.PublishDataSource
import io.reactivex.Observable
import io.reactivex.functions.BiFunction
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private val initialState: RoomMemberProfileViewState, class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private val initialState: RoomMemberProfileViewState,
private val stringProvider: StringProvider,
private val session: Session) private val session: Session)
: VectorViewModel<RoomMemberProfileViewState, RoomMemberProfileAction>(initialState) { : VectorViewModel<RoomMemberProfileViewState, RoomMemberProfileAction>(initialState) {
@ -53,6 +71,9 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
} }
} }
private val _viewEvents = PublishDataSource<RoomMemberProfileViewEvents>()
val viewEvents: DataSource<RoomMemberProfileViewEvents> = _viewEvents
private val room = if (initialState.roomId != null) { private val room = if (initialState.roomId != null) {
session.getRoom(initialState.roomId) session.getRoom(initialState.roomId)
} else { } else {
@ -61,65 +82,118 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
init { init {
setState { copy(isMine = session.myUserId == this.userId) } setState { copy(isMine = session.myUserId == this.userId) }
observeRoomSummary() observeIgnoredState()
observeRoomMemberSummary() viewModelScope.launch(Dispatchers.Main) {
observePowerLevel() // Do we have a room member for this id.
fetchProfileInfoIfRequired() val roomMember = withContext(Dispatchers.Default) {
room?.getRoomMember(initialState.userId)
}
// If not, we look for profile info on the server
if (room == null || roomMember == null) {
fetchProfileInfo()
} else {
// otherwise we just start listening to db
setState { copy(showAsMember = true) }
observeRoomMemberSummary(room)
observeRoomSummaryAndPowerLevels(room)
}
}
} }
private fun fetchProfileInfoIfRequired() { private fun observeIgnoredState() {
val roomMember = room?.getRoomMember(initialState.userId) session.rx().liveIgnoredUsers()
if (roomMember != null) { .map { ignored ->
return ignored.find {
} it.userId == initialState.userId
session.rx().getProfileInfo(initialState.userId) } != null
}
.execute { .execute {
copy(profileInfo = it) copy(isIgnored = it)
} }
} }
override fun handle(action: RoomMemberProfileAction) { override fun handle(action: RoomMemberProfileAction) {
Timber.v("Handle $action") when (action) {
RoomMemberProfileAction.RetryFetchingInfo -> fetchProfileInfo()
is RoomMemberProfileAction.IgnoreUser -> handleIgnoreAction()
}
} }
private fun observeRoomSummary() { private fun observeRoomMemberSummary(room: Room) {
if (room == null) {
return
}
room.rx().liveRoomSummary()
.unwrap()
.execute {
copy(roomSummary = it)
}
}
private fun observeRoomMemberSummary() {
if (room == null) {
return
}
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().liveRoomMembers(queryParams)
.map { it.firstOrNull().toOptional() } .map { it.firstOrNull()?.toMatrixItem().toOptional() }
.unwrap() .unwrap()
.execute { .execute {
copy(roomMemberSummary = it) copy(userMatrixItem = it)
} }
} }
private fun observePowerLevel() { private fun fetchProfileInfo() {
if (room == null) { session.rx().getProfileInfo(initialState.userId)
return .map {
} MatrixItem.UserItem(
room.rx() id = initialState.userId,
.liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS) displayName = it[ProfileService.DISPLAY_NAME_KEY] as? String,
avatarUrl = it[ProfileService.AVATAR_URL_KEY] as? String
)
}
.execute {
copy(userMatrixItem = it)
}
}
private fun observeRoomSummaryAndPowerLevels(room: Room) {
val roomSummaryLive = room.rx().liveRoomSummary().unwrap()
val powerLevelsContentLive = room.rx().liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS)
.mapOptional { it.content.toModel<PowerLevelsContent>() } .mapOptional { it.content.toModel<PowerLevelsContent>() }
.unwrap() .unwrap()
.execute {
copy(powerLevelsContent = it) roomSummaryLive.execute {
copy(isRoomEncrypted = it.invoke()?.isEncrypted == true)
}
Observable
.combineLatest(
roomSummaryLive,
powerLevelsContentLive,
BiFunction<RoomSummary, PowerLevelsContent, String> { roomSummary, powerLevelsContent ->
val roomName = roomSummary.toMatrixItem().getBestName()
val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent)
val userPowerLevel = powerLevelsHelper.getUserPowerLevel(initialState.userId)
if (userPowerLevel == PowerLevelsConstants.DEFAULT_ROOM_ADMIN_LEVEL) {
stringProvider.getString(R.string.room_member_power_level_admin_in, roomName)
} else if (userPowerLevel == PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL) {
stringProvider.getString(R.string.room_member_power_level_moderator_in, roomName)
} else if (userPowerLevel == PowerLevelsConstants.DEFAULT_ROOM_USER_LEVEL) {
""
} else {
stringProvider.getString(R.string.room_member_power_level_custom_in, userPowerLevel, roomName)
}
}
).execute {
copy(userPowerLevelString = it)
} }
} }
private fun handleIgnoreAction() = withState { state ->
val isIgnored = state.isIgnored() ?: return@withState
_viewEvents.post(RoomMemberProfileViewEvents.Loading(stringProvider.getString(R.string.please_wait)))
val ignoreActionCallback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
_viewEvents.post(RoomMemberProfileViewEvents.OnIgnoreActionSuccess)
}
override fun onFailure(failure: Throwable) {
_viewEvents.post(RoomMemberProfileViewEvents.Failure(failure))
}
}
if (isIgnored) {
session.unIgnoreUserIds(listOf(state.userId), ignoreActionCallback)
} else {
session.ignoreUserIds(listOf(initialState.userId), ignoreActionCallback)
}
}
} }

View File

@ -20,33 +20,23 @@ package im.vector.riotx.features.roommemberprofile
import com.airbnb.mvrx.Async import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.profile.ProfileService
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.api.util.MatrixItem import im.vector.matrix.android.api.util.MatrixItem
import im.vector.matrix.android.api.util.toMatrixItem
typealias ProfileInfo = JsonDict
data class RoomMemberProfileViewState( data class RoomMemberProfileViewState(
val userId: String, val userId: String,
val roomId: String?, val roomId: String?,
val showAsMember: Boolean = false,
val isMine: Boolean = false, val isMine: Boolean = false,
val roomSummary: Async<RoomSummary> = Uninitialized, val isIgnored: Async<Boolean> = Uninitialized,
val roomMemberSummary: Async<RoomMemberSummary> = Uninitialized, val isRoomEncrypted: Boolean = false,
val profileInfo: Async<ProfileInfo> = Uninitialized, val userPowerLevelString: Async<String> = Uninitialized,
val powerLevelsContent: Async<PowerLevelsContent> = Uninitialized val userMatrixItem: Async<MatrixItem> = Uninitialized
) : MvRxState { ) : MvRxState {
constructor(args: RoomMemberProfileArgs) : this(roomId = args.roomId, userId = args.userId) constructor(args: RoomMemberProfileArgs) : this(roomId = args.roomId, userId = args.userId)
fun memberAsMatrixItem(): MatrixItem? {
return roomMemberSummary()?.toMatrixItem() ?: profileInfo()?.let {
MatrixItem.UserItem(userId, it[ProfileService.DISPLAY_NAME_KEY] as? String, it[ProfileService.AVATAR_URL_KEY] as? String)
}
}
} }

View File

@ -21,8 +21,8 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.extensions.commitTransaction import im.vector.riotx.core.extensions.addFragment
import im.vector.riotx.core.extensions.commitTransactionNow import im.vector.riotx.core.extensions.addFragmentToBackstack
import im.vector.riotx.core.platform.ToolbarConfigurable import im.vector.riotx.core.platform.ToolbarConfigurable
import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment
@ -32,9 +32,6 @@ class RoomProfileActivity : VectorBaseActivity(), ToolbarConfigurable {
companion object { companion object {
private const val EXTRA_ROOM_PROFILE_ARGS = "EXTRA_ROOM_PROFILE_ARGS" private const val EXTRA_ROOM_PROFILE_ARGS = "EXTRA_ROOM_PROFILE_ARGS"
private const val TAG_ROOM_PROFILE_FRAGMENT = "TAG_ROOM_PROFILE_FRAGMENT"
private const val TAG_ROOM_MEMBER_LIST_FRAGMENT = "TAG_ROOM_MEMBER_LIST_FRAGMENT"
fun newIntent(context: Context, roomId: String): Intent { fun newIntent(context: Context, roomId: String): Intent {
val roomProfileArgs = RoomProfileArgs(roomId) val roomProfileArgs = RoomProfileArgs(roomId)
@ -53,14 +50,7 @@ class RoomProfileActivity : VectorBaseActivity(), ToolbarConfigurable {
sharedActionViewModel = viewModelProvider.get(RoomProfileSharedActionViewModel::class.java) sharedActionViewModel = viewModelProvider.get(RoomProfileSharedActionViewModel::class.java)
roomProfileArgs = intent?.extras?.getParcelable(EXTRA_ROOM_PROFILE_ARGS) ?: return roomProfileArgs = intent?.extras?.getParcelable(EXTRA_ROOM_PROFILE_ARGS) ?: return
if (isFirstCreation()) { if (isFirstCreation()) {
val argsBundle = roomProfileArgs.toMvRxBundle() addFragment(R.id.simpleFragmentContainer, RoomProfileFragment::class.java, roomProfileArgs)
val roomProfileFragment = createFragment(RoomProfileFragment::class.java, argsBundle)
val roomMemberListFragment = createFragment(RoomMemberListFragment::class.java, argsBundle)
supportFragmentManager.commitTransactionNow {
add(R.id.simpleFragmentContainer, roomProfileFragment, TAG_ROOM_PROFILE_FRAGMENT)
add(R.id.simpleFragmentContainer, roomMemberListFragment, TAG_ROOM_MEMBER_LIST_FRAGMENT)
detach(roomMemberListFragment)
}
} }
sharedActionViewModel sharedActionViewModel
.observe() .observe()
@ -83,15 +73,7 @@ class RoomProfileActivity : VectorBaseActivity(), ToolbarConfigurable {
} }
private fun openRoomMembers() { private fun openRoomMembers() {
val roomProfileFragment = supportFragmentManager.findFragmentByTag(TAG_ROOM_PROFILE_FRAGMENT) addFragmentToBackstack(R.id.simpleFragmentContainer, RoomMemberListFragment::class.java, roomProfileArgs)
?: throw IllegalStateException("You should have a RoomProfileFragment")
val roomMemberListFragment = supportFragmentManager.findFragmentByTag(TAG_ROOM_MEMBER_LIST_FRAGMENT)
?: throw IllegalStateException("You should have a RoomMemberListFragment")
supportFragmentManager.commitTransaction {
hide(roomProfileFragment)
attach(roomMemberListFragment)
addToBackStack(null)
}
} }
override fun configure(toolbar: Toolbar) { override fun configure(toolbar: Toolbar) {

View File

@ -19,7 +19,6 @@
package im.vector.riotx.features.roomprofile package im.vector.riotx.features.roomprofile
import android.app.ProgressDialog
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.View import android.view.View
@ -58,7 +57,6 @@ class RoomProfileFragment @Inject constructor(
val roomProfileViewModelFactory: RoomProfileViewModel.Factory val roomProfileViewModelFactory: RoomProfileViewModel.Factory
) : VectorBaseFragment(), RoomProfileController.Callback { ) : VectorBaseFragment(), RoomProfileController.Callback {
private var progress: ProgressDialog? = null
private val roomProfileArgs: RoomProfileArgs by args() private val roomProfileArgs: RoomProfileArgs by args()
private lateinit var roomListQuickActionsSharedActionViewModel: RoomListQuickActionsSharedActionViewModel private lateinit var roomListQuickActionsSharedActionViewModel: RoomListQuickActionsSharedActionViewModel
private lateinit var roomProfileSharedActionViewModel: RoomProfileSharedActionViewModel private lateinit var roomProfileSharedActionViewModel: RoomProfileSharedActionViewModel
@ -73,36 +71,32 @@ class RoomProfileFragment @Inject constructor(
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
roomListQuickActionsSharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java) roomListQuickActionsSharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java)
roomProfileSharedActionViewModel = activityViewModelProvider.get(RoomProfileSharedActionViewModel::class.java) roomProfileSharedActionViewModel = activityViewModelProvider.get(RoomProfileSharedActionViewModel::class.java)
matrixProfileHeaderView.apply { val headerView = matrixProfileHeaderView.let {
layoutResource = R.layout.view_stub_room_profile_header it.layoutResource = R.layout.view_stub_room_profile_header
inflate() it.inflate()
} }
setupToolbar(matrixProfileToolbar)
setupRecyclerView() setupRecyclerView()
appBarStateChangeListener = MatrixItemAppBarStateChangeListener(matrixProfileCollapsingToolbarLayout.scrimAnimationDuration, listOf(matrixProfileToolbarAvatarImageView, matrixProfileToolbarTitleView)) appBarStateChangeListener = MatrixItemAppBarStateChangeListener(headerView, listOf(matrixProfileToolbarAvatarImageView,
matrixProfileToolbarTitleView))
matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener) matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener)
roomProfileViewModel.viewEvents roomProfileViewModel.viewEvents
.observe() .observe()
.subscribe { .subscribe {
progress?.dismiss() dismissLoadingDialog()
when (it) { when (it) {
RoomProfileViewEvents.Loading -> showLoading() is RoomProfileViewEvents.Loading -> showLoadingDialog(it.message)
RoomProfileViewEvents.OnLeaveRoomSuccess -> onLeaveRoom() RoomProfileViewEvents.OnLeaveRoomSuccess -> onLeaveRoom()
is RoomProfileViewEvents.Failure -> showError(it.throwable) is RoomProfileViewEvents.Failure -> showError(it.throwable)
} }
} }
.disposeOnDestroyView() .disposeOnDestroyView()
roomListQuickActionsSharedActionViewModel roomListQuickActionsSharedActionViewModel
.observe() .observe()
.subscribe { handleQuickActions(it) } .subscribe { handleQuickActions(it) }
.disposeOnDestroyView() .disposeOnDestroyView()
} }
override fun onResume() {
super.onResume()
setupToolbar(matrixProfileToolbar)
}
private fun handleQuickActions(action: RoomListQuickActionsSharedAction) = when (action) { private fun handleQuickActions(action: RoomListQuickActionsSharedAction) = when (action) {
is RoomListQuickActionsSharedAction.NotificationsAllNoisy -> { is RoomListQuickActionsSharedAction.NotificationsAllNoisy -> {
roomProfileViewModel.handle(RoomProfileAction.ChangeRoomNotificationState(RoomNotificationState.ALL_MESSAGES_NOISY)) roomProfileViewModel.handle(RoomProfileAction.ChangeRoomNotificationState(RoomNotificationState.ALL_MESSAGES_NOISY))
@ -124,15 +118,7 @@ class RoomProfileFragment @Inject constructor(
} }
private fun showError(throwable: Throwable) { private fun showError(throwable: Throwable) {
vectorBaseActivity.showSnackbar(errorFormatter.toHumanReadable(throwable)) showErrorInSnackbar(throwable)
}
private fun showLoading() {
progress = ProgressDialog(requireContext()).apply {
setMessage(getString(R.string.room_profile_leaving_room))
setProgressStyle(ProgressDialog.STYLE_SPINNER)
show()
}
} }
private fun setupRecyclerView() { private fun setupRecyclerView() {

View File

@ -19,7 +19,7 @@ package im.vector.riotx.features.roomprofile
* Transient events for RoomProfile * Transient events for RoomProfile
*/ */
sealed class RoomProfileViewEvents { sealed class RoomProfileViewEvents {
object Loading: RoomProfileViewEvents() data class Loading(val message: CharSequence): RoomProfileViewEvents()
object OnLeaveRoomSuccess: RoomProfileViewEvents() object OnLeaveRoomSuccess: RoomProfileViewEvents()
data class Failure(val throwable: Throwable) : RoomProfileViewEvents() data class Failure(val throwable: Throwable) : RoomProfileViewEvents()

View File

@ -26,13 +26,16 @@ import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
import im.vector.matrix.rx.unwrap import im.vector.matrix.rx.unwrap
import im.vector.riotx.R
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.utils.DataSource import im.vector.riotx.core.utils.DataSource
import im.vector.riotx.core.utils.PublishDataSource import im.vector.riotx.core.utils.PublishDataSource
import im.vector.riotx.features.home.room.list.RoomListAction import im.vector.riotx.features.home.room.list.RoomListAction
import im.vector.riotx.features.home.room.list.RoomListViewEvents import im.vector.riotx.features.home.room.list.RoomListViewEvents
class RoomProfileViewModel @AssistedInject constructor(@Assisted initialState: RoomProfileViewState, class RoomProfileViewModel @AssistedInject constructor(@Assisted initialState: RoomProfileViewState,
private val stringProvider: StringProvider,
private val session: Session) private val session: Session)
: VectorViewModel<RoomProfileViewState, RoomProfileAction>(initialState) { : VectorViewModel<RoomProfileViewState, RoomProfileAction>(initialState) {
@ -84,7 +87,7 @@ class RoomProfileViewModel @AssistedInject constructor(@Assisted initialState: R
} }
private fun handleLeaveRoom() { private fun handleLeaveRoom() {
_viewEvents.post(RoomProfileViewEvents.Loading) _viewEvents.post(RoomProfileViewEvents.Loading(stringProvider.getString(R.string.room_profile_leaving_room)))
room.leave(null, object : MatrixCallback<Unit> { room.leave(null, object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) { override fun onSuccess(data: Unit) {
_viewEvents.post(RoomProfileViewEvents.OnLeaveRoomSuccess) _viewEvents.post(RoomProfileViewEvents.OnLeaveRoomSuccess)

View File

@ -48,12 +48,8 @@ class RoomMemberListFragment @Inject constructor(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
roomMemberListController.callback = this roomMemberListController.callback = this
recyclerView.configureWith(roomMemberListController, hasFixedSize = true)
}
override fun onResume() {
super.onResume()
setupToolbar(roomMemberListToolbar) setupToolbar(roomMemberListToolbar)
recyclerView.configureWith(roomMemberListController, hasFixedSize = true)
} }
override fun onDestroyView() { override fun onDestroyView() {

View File

@ -2,6 +2,7 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" 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/vector_coordinator_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?riotx_header_panel_background"> android:background="?riotx_header_panel_background">
@ -19,6 +20,7 @@
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:titleEnabled="false" app:titleEnabled="false"
app:toolbarId="@+id/matrixProfileToolbar"> app:toolbarId="@+id/matrixProfileToolbar">
@ -40,6 +42,7 @@
app:layout_collapseMode="pin"> app:layout_collapseMode="pin">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/matrixProfileToolbarContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@ -50,10 +53,10 @@
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:alpha="0" android:alpha="0"
tools:alpha="1"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:alpha="1"
tools:src="@tools:sample/avatars" /> tools:src="@tools:sample/avatars" />
<TextView <TextView
@ -62,9 +65,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:ellipsize="end"
android:alpha="0" android:alpha="0"
tools:alpha="1" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:textColor="?vctr_toolbar_primary_text_color" android:textColor="?vctr_toolbar_primary_text_color"
android:textSize="18sp" android:textSize="18sp"
@ -72,6 +74,7 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/matrixProfileToolbarAvatarImageView" app:layout_constraintStart_toEndOf="@+id/matrixProfileToolbarAvatarImageView"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:alpha="1"
tools:text="@sample/matrix.json/data/roomName" /> tools:text="@sample/matrix.json/data/roomName" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -30,7 +30,7 @@
<TextView <TextView
android:id="@+id/actionTitle" android:id="@+id/actionTitle"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
@ -44,13 +44,13 @@
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/actionIcon" app:layout_constraintStart_toEndOf="@id/actionIcon"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_default="wrap" app:layout_constrainedWidth="true"
app:layout_goneMarginStart="0dp" app:layout_goneMarginStart="0dp"
tools:text="Learn more" /> tools:text="Learn more" />
<TextView <TextView
android:id="@+id/actionSubtitle" android:id="@+id/actionSubtitle"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
@ -64,7 +64,7 @@
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/actionIcon" app:layout_constraintStart_toEndOf="@id/actionIcon"
app:layout_constraintTop_toBottomOf="@id/actionTitle" app:layout_constraintTop_toBottomOf="@id/actionTitle"
app:layout_constraintWidth_default="wrap" app:layout_constrainedWidth="true"
app:layout_goneMarginStart="0dp" app:layout_goneMarginStart="0dp"
tools:text="Messages in this room are not end-to-end encrypted" /> tools:text="Messages in this room are not end-to-end encrypted" />

View File

@ -27,7 +27,7 @@
<TextView <TextView
android:id="@+id/matrixItemTitle" android:id="@+id/matrixItemTitle"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
@ -41,13 +41,13 @@
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/matrixItemAvatar" app:layout_constraintStart_toEndOf="@id/matrixItemAvatar"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_default="wrap" app:layout_constrainedWidth="true"
app:layout_goneMarginStart="0dp" app:layout_goneMarginStart="0dp"
tools:text="@sample/matrix.json/data/displayName" /> tools:text="@sample/matrix.json/data/displayName" />
<TextView <TextView
android:id="@+id/matrixItemSubtitle" android:id="@+id/matrixItemSubtitle"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
@ -61,7 +61,7 @@
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/matrixItemAvatar" app:layout_constraintStart_toEndOf="@id/matrixItemAvatar"
app:layout_constraintTop_toBottomOf="@id/matrixItemTitle" app:layout_constraintTop_toBottomOf="@id/matrixItemTitle"
app:layout_constraintWidth_default="wrap" app:layout_constrainedWidth="true"
app:layout_goneMarginStart="0dp" app:layout_goneMarginStart="0dp"
tools:text="@sample/matrix.json/data/mxid" /> tools:text="@sample/matrix.json/data/mxid" />

View File

@ -1,70 +1,75 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/matrixProfileHeaderView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/memberProfileStateView"
android:background="?riotx_background" android:background="?riotx_background"
android:padding="16dp" android:padding="16dp">
android:orientation="vertical">
<LinearLayout
<ImageView android:id="@+id/memberProfileInfoContainer"
android:id="@+id/memberProfileAvatarView" android:layout_width="match_parent"
android:layout_width="128dp"
android:layout_height="128dp"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="16dp"
tools:src="@tools:sample/avatars" />
<TextView
android:id="@+id/memberProfileNameView"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:orientation="vertical">
android:gravity="center_vertical"
android:singleLine="true"
android:textAppearance="@style/Vector.Toolbar.Title"
android:textSize="20sp"
android:textStyle="bold"
tools:text="@sample/matrix.json/data/displayName" />
<TextView <ImageView
android:id="@+id/memberProfileIdView" android:id="@+id/memberProfileAvatarView"
android:layout_width="wrap_content" android:layout_width="128dp"
android:layout_height="wrap_content" android:layout_height="128dp"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp" android:layout_marginBottom="16dp"
android:layout_marginBottom="16dp" tools:src="@tools:sample/avatars" />
android:singleLine="true"
android:textStyle="bold"
android:textAppearance="@style/Vector.Toolbar.Title"
android:textSize="14sp"
tools:text="@sample/matrix.json/data/mxid" />
<TextView <TextView
android:id="@+id/memberProfilePowerLevelView" 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:layout_gravity="center_horizontal"
android:layout_marginStart="40dp" android:gravity="center_vertical"
android:layout_marginEnd="40dp" android:singleLine="true"
android:layout_marginBottom="16dp" android:textAppearance="@style/Vector.Toolbar.Title"
android:gravity="center" android:textSize="20sp"
android:textSize="12sp" android:textStyle="bold"
tools:text="Admin in Matrix" /> tools:text="@sample/matrix.json/data/displayName" />
<TextView <TextView
android:id="@+id/memberProfileStatusView" 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_gravity="center_horizontal"
android:layout_marginStart="40dp" android:layout_marginTop="8dp"
android:layout_marginEnd="40dp" android:layout_marginBottom="16dp"
android:gravity="center" android:singleLine="true"
android:textSize="14sp" android:textAppearance="@style/Vector.Toolbar.Title"
android:visibility="gone" android:textSize="14sp"
tools:text="Here is a room topic, it can be multi-line but should always be displayed in full 🍱" /> android:textStyle="bold"
tools:text="@sample/matrix.json/data/mxid" />
</LinearLayout> <TextView
android:id="@+id/memberProfilePowerLevelView"
android:layout_width="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:gravity="center"
android:textSize="12sp"
tools:text="Admin in Matrix" />
<TextView
android:id="@+id/memberProfileStatusView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginStart="40dp"
android:layout_marginEnd="40dp"
android:gravity="center"
android:textSize="14sp"
android:visibility="gone"
tools:text="Here is a room topic, it can be multi-line but should always be displayed in full 🍱" />
</LinearLayout>
</im.vector.riotx.core.platform.StateView>

View File

@ -47,5 +47,7 @@
<string name="room_member_jump_to_read_receipt">Jump to read receipt</string> <string name="room_member_jump_to_read_receipt">Jump to read receipt</string>
<string name="unignore">Unignore</string>
</resources> </resources>