Room member profile: branch the UI and fix some UI issues

This commit is contained in:
ganfra 2020-01-13 16:49:14 +01:00
parent 171ec4fbdc
commit ae1a24e948
23 changed files with 586 additions and 151 deletions

View File

@ -28,17 +28,20 @@ fun roomMemberQueryParams(init: (RoomMemberQueryParams.Builder.() -> Unit) = {})
*/ */
data class RoomMemberQueryParams( data class RoomMemberQueryParams(
val displayName: QueryStringValue, val displayName: QueryStringValue,
val memberships: List<Membership> val memberships: List<Membership>,
val userId: QueryStringValue
) { ) {
class Builder { class Builder {
var userId: QueryStringValue = QueryStringValue.NoCondition
var displayName: QueryStringValue = QueryStringValue.IsNotEmpty var displayName: QueryStringValue = QueryStringValue.IsNotEmpty
var memberships: List<Membership> = Membership.all() var memberships: List<Membership> = Membership.all()
fun build() = RoomMemberQueryParams( fun build() = RoomMemberQueryParams(
displayName = displayName, displayName = displayName,
memberships = memberships memberships = memberships,
userId = userId
) )
} }
} }

View File

@ -93,6 +93,7 @@ internal class DefaultMembershipService @AssistedInject constructor(@Assisted pr
private fun roomMembersQuery(realm: Realm, queryParams: RoomMemberQueryParams): RealmQuery<RoomMemberSummaryEntity> { private fun roomMembersQuery(realm: Realm, queryParams: RoomMemberQueryParams): RealmQuery<RoomMemberSummaryEntity> {
return RoomMemberHelper(realm, roomId).queryRoomMembersEvent() return RoomMemberHelper(realm, roomId).queryRoomMembersEvent()
.process(RoomMemberSummaryEntityFields.USER_ID, queryParams.userId)
.process(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) .process(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
.process(RoomMemberSummaryEntityFields.DISPLAY_NAME, queryParams.displayName) .process(RoomMemberSummaryEntityFields.DISPLAY_NAME, queryParams.displayName)
} }

View File

@ -0,0 +1,36 @@
/*
* 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.core.animations
import android.view.View
import com.google.android.material.appbar.AppBarLayout
class MatrixItemAppBarStateChangeListener(private val animationDuration: Long, private val views: List<View>) : AppBarStateChangeListener() {
override fun onStateChanged(appBarLayout: AppBarLayout, state: State) {
if (state == State.COLLAPSED) {
views.forEach {
it.animate().alpha(1f).duration = animationDuration + 100
}
} else {
views.forEach {
it.animate().alpha(0f).duration = animationDuration - 100
}
}
}
}

View File

@ -32,6 +32,7 @@ fun EpoxyController.buildProfileAction(
id: String, id: String,
title: String, title: String,
subtitle: String? = null, subtitle: String? = null,
editable: Boolean = true,
@DrawableRes icon: Int = 0, @DrawableRes icon: Int = 0,
destructive: Boolean = false, destructive: Boolean = false,
divider: Boolean = true, divider: Boolean = true,
@ -42,6 +43,7 @@ fun EpoxyController.buildProfileAction(
iconRes(icon) iconRes(icon)
id("action_$id") id("action_$id")
subtitle(subtitle) subtitle(subtitle)
editable(editable)
destructive(destructive) destructive(destructive)
title(title) title(title)
listener { _ -> listener { _ ->

View File

@ -23,11 +23,18 @@ import android.os.Parcelable
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.annotation.* import androidx.annotation.AttrRes
import androidx.annotation.LayoutRes
import androidx.annotation.MainThread
import androidx.annotation.MenuRes
import androidx.annotation.Nullable
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentFactory
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
@ -41,7 +48,12 @@ import com.google.android.material.snackbar.Snackbar
import im.vector.matrix.android.api.failure.GlobalError import im.vector.matrix.android.api.failure.GlobalError
import im.vector.riotx.BuildConfig import im.vector.riotx.BuildConfig
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.* import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.di.DaggerScreenComponent
import im.vector.riotx.core.di.HasScreenInjector
import im.vector.riotx.core.di.HasVectorInjector
import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.di.VectorComponent
import im.vector.riotx.core.dialogs.DialogLocker import im.vector.riotx.core.dialogs.DialogLocker
import im.vector.riotx.core.extensions.observeEvent import im.vector.riotx.core.extensions.observeEvent
import im.vector.riotx.core.utils.toast import im.vector.riotx.core.utils.toast
@ -92,6 +104,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
lateinit var rageShake: RageShake lateinit var rageShake: RageShake
private set private set
protected lateinit var navigator: Navigator protected lateinit var navigator: Navigator
private lateinit var fragmentFactory: FragmentFactory
private lateinit var activeSessionHolder: ActiveSessionHolder private lateinit var activeSessionHolder: ActiveSessionHolder
private lateinit var vectorPreferences: VectorPreferences private lateinit var vectorPreferences: VectorPreferences
@ -145,7 +158,8 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
} }
Timber.v("Injecting dependencies into ${javaClass.simpleName} took $timeForInjection ms") Timber.v("Injecting dependencies into ${javaClass.simpleName} took $timeForInjection ms")
ThemeUtils.setActivityTheme(this, getOtherThemes()) ThemeUtils.setActivityTheme(this, getOtherThemes())
supportFragmentManager.fragmentFactory = screenComponent.fragmentFactory() fragmentFactory = screenComponent.fragmentFactory()
supportFragmentManager.fragmentFactory = fragmentFactory
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
viewModelFactory = screenComponent.viewModelFactory() viewModelFactory = screenComponent.viewModelFactory()
configurationViewModel = viewModelProvider.get(ConfigurationViewModel::class.java) configurationViewModel = viewModelProvider.get(ConfigurationViewModel::class.java)
@ -196,7 +210,8 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
handleInvalidToken(globalError) handleInvalidToken(globalError)
is GlobalError.ConsentNotGivenError -> is GlobalError.ConsentNotGivenError ->
consentNotGivenHelper.displayDialog(globalError.consentUri, consentNotGivenHelper.displayDialog(globalError.consentUri,
activeSessionHolder.getActiveSession().sessionParams.homeServerConnectionConfig.homeServerUri.host ?: "") activeSessionHolder.getActiveSession().sessionParams.homeServerConnectionConfig.homeServerUri.host
?: "")
} }
} }
@ -209,11 +224,11 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
mainActivityStarted = true mainActivityStarted = true
MainActivity.restartApp(this, MainActivity.restartApp(this,
MainActivityArgs( MainActivityArgs(
clearCredentials = !globalError.softLogout, clearCredentials = !globalError.softLogout,
isUserLoggedOut = true, isUserLoggedOut = true,
isSoftLogout = globalError.softLogout isSoftLogout = globalError.softLogout
) )
) )
} }
@ -276,6 +291,12 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
protected open fun injectWith(injector: ScreenComponent) = Unit protected open fun injectWith(injector: ScreenComponent) = Unit
protected fun createFragment(fragmentClass: Class<out Fragment>, args: Bundle?): Fragment {
return fragmentFactory.instantiate(classLoader, fragmentClass.name).apply {
arguments = args
}
}
/* ========================================================================================== /* ==========================================================================================
* PRIVATE METHODS * PRIVATE METHODS
* ========================================================================================== */ * ========================================================================================== */

View File

@ -96,6 +96,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
} }
final override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { final override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
Timber.i("onCreateView Fragment ${this.javaClass.simpleName}")
return inflater.inflate(getLayoutResId(), container, false) return inflater.inflate(getLayoutResId(), container, false)
} }
@ -117,6 +118,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
@CallSuper @CallSuper
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
Timber.i("onDestroyView Fragment ${this.javaClass.simpleName}")
mUnBinder?.unbind() mUnBinder?.unbind()
mUnBinder = null mUnBinder = null
uiDisposables.clear() uiDisposables.clear()

View File

@ -83,7 +83,7 @@ class DefaultNavigator @Inject constructor(
} }
override fun openRoomMemberProfile(userId: String, roomId: String?, context: Context, buildTask: Boolean) { override fun openRoomMemberProfile(userId: String, roomId: String?, context: Context, buildTask: Boolean) {
val args = RoomMemberProfileArgs(userId = userId) val args = RoomMemberProfileArgs(userId = userId, roomId = roomId)
context.startActivity(RoomMemberProfileActivity.newIntent(context, args)) context.startActivity(RoomMemberProfileActivity.newIntent(context, args))
} }

View File

@ -19,4 +19,13 @@ package im.vector.riotx.features.roommemberprofile
import im.vector.riotx.core.platform.VectorViewModelAction import im.vector.riotx.core.platform.VectorViewModelAction
sealed class RoomMemberProfileAction : VectorViewModelAction sealed class RoomMemberProfileAction : VectorViewModelAction {
sealed class Displayable : RoomMemberProfileAction() {
object JumpToReadReceipt : Displayable()
object Ignore : Displayable()
object Mention : Displayable()
}
}

View File

@ -0,0 +1,107 @@
/*
* 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
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.profiles.buildProfileAction
import im.vector.riotx.core.epoxy.profiles.buildProfileSection
import im.vector.riotx.core.resources.StringProvider
import javax.inject.Inject
class RoomMemberProfileController @Inject constructor(private val stringProvider: StringProvider)
: TypedEpoxyController<RoomMemberProfileViewState>() {
var callback: Callback? = null
interface Callback {
fun onIgnoreClicked()
fun onLearnMoreClicked()
fun onJumpToReadReceiptClicked()
fun onMentionClicked()
}
override fun buildModels(data: RoomMemberProfileViewState?) {
if (data == null) {
return
}
if (data.roomId == null) {
buildUserActions()
} else {
buildRoomMemberActions(data)
}
}
private fun buildUserActions() {
// More
buildProfileSection(stringProvider.getString(R.string.room_profile_section_more))
buildProfileAction(
id = "ignore",
title = stringProvider.getString(R.string.ignore),
destructive = true,
editable = false,
action = { callback?.onIgnoreClicked() }
)
}
private fun buildRoomMemberActions(data: RoomMemberProfileViewState) {
val roomSummaryEntity = data.roomSummary() ?: return
// Security
buildProfileSection(stringProvider.getString(R.string.room_profile_section_security))
val learnMoreSubtitle = if (roomSummaryEntity.isEncrypted) {
R.string.room_profile_encrypted_subtitle
} else {
R.string.room_profile_not_encrypted_subtitle
}
buildProfileAction(
id = "learn_more",
title = stringProvider.getString(R.string.room_profile_section_security_learn_more),
editable = false,
subtitle = stringProvider.getString(learnMoreSubtitle),
action = { callback?.onLearnMoreClicked() }
)
// More
if (!data.isMine) {
buildProfileSection(stringProvider.getString(R.string.room_profile_section_more))
buildProfileAction(
id = "read_receipt",
editable = false,
title = stringProvider.getString(R.string.room_member_jump_to_read_receipt),
action = { callback?.onJumpToReadReceiptClicked() }
)
buildProfileAction(
id = "mention",
title = stringProvider.getString(R.string.room_participants_action_mention),
editable = false,
action = { callback?.onMentionClicked() }
)
buildProfileAction(
id = "ignore",
title = stringProvider.getString(R.string.ignore),
destructive = true,
editable = false,
action = { callback?.onIgnoreClicked() }
)
}
}
}

View File

@ -19,14 +19,25 @@ package im.vector.riotx.features.roommemberprofile
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
import com.airbnb.mvrx.args
import android.view.View import android.view.View
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
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.MatrixItemAppBarStateChangeListener
import im.vector.riotx.core.extensions.cleanup
import im.vector.riotx.core.extensions.configureWith
import im.vector.riotx.core.extensions.setTextOrHide
import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.platform.VectorBaseFragment
import timber.log.Timber import im.vector.riotx.features.home.AvatarRenderer
import kotlinx.android.parcel.Parcelize
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 javax.inject.Inject import javax.inject.Inject
@Parcelize @Parcelize
@ -36,21 +47,87 @@ data class RoomMemberProfileArgs(
) : Parcelable ) : Parcelable
class RoomMemberProfileFragment @Inject constructor( class RoomMemberProfileFragment @Inject constructor(
val viewModelFactory: RoomMemberProfileViewModel.Factory val viewModelFactory: RoomMemberProfileViewModel.Factory,
) : VectorBaseFragment() { private val roomMemberProfileController: RoomMemberProfileController,
private val avatarRenderer: AvatarRenderer
) : VectorBaseFragment(), RoomMemberProfileController.Callback {
private val fragmentArgs: RoomMemberProfileArgs by args() private val fragmentArgs: RoomMemberProfileArgs by args()
private val viewModel: RoomMemberProfileViewModel by fragmentViewModel() private val viewModel: RoomMemberProfileViewModel by fragmentViewModel()
override fun getLayoutResId() = R.layout.fragment_room_member_profile private lateinit var appBarStateChangeListener: AppBarStateChangeListener
override fun getLayoutResId() = R.layout.fragment_matrix_profile
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
// Initialize your view, subscribe to viewModel... setupToolbar(matrixProfileToolbar)
matrixProfileHeaderView.apply {
layoutResource = R.layout.view_stub_room_member_profile_header
inflate()
}
matrixProfileRecyclerView.configureWith(roomMemberProfileController, hasFixedSize = true)
roomMemberProfileController.callback = this
appBarStateChangeListener = MatrixItemAppBarStateChangeListener(matrixProfileCollapsingToolbarLayout.scrimAnimationDuration, listOf(matrixProfileToolbarAvatarImageView, matrixProfileToolbarTitleView))
matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener)
} }
override fun onDestroyView() {
matrixProfileAppBarLayout.removeOnOffsetChangedListener(appBarStateChangeListener)
roomMemberProfileController.callback = null
matrixProfileRecyclerView.cleanup()
super.onDestroyView()
}
override fun invalidate() = withState(viewModel) { state -> override fun invalidate() = withState(viewModel) { state ->
Timber.v("Invalidate: $state") val memberMatrixItem = state.memberAsMatrixItem() ?: return@withState
memberProfileIdView.text = memberMatrixItem.id
val bestName = memberMatrixItem.getBestName()
memberProfileNameView.text = bestName
matrixProfileToolbarTitleView.text = bestName
avatarRenderer.render(memberMatrixItem, memberProfileAvatarView)
avatarRenderer.render(memberMatrixItem, matrixProfileToolbarAvatarImageView)
val roomSummary = state.roomSummary()
val powerLevelsContent = state.powerLevelsContent()
if (powerLevelsContent == null || roomSummary == null) {
memberProfilePowerLevelView.visibility = View.GONE
} else {
val roomName = roomSummary.toMatrixItem().getBestName()
val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent)
val userPowerLevel = powerLevelsHelper.getUserPowerLevel(state.userId)
val powerLevelText = if (userPowerLevel == PowerLevelsConstants.DEFAULT_ROOM_ADMIN_LEVEL) {
getString(R.string.room_member_power_level_admin_in, roomName)
} else if (userPowerLevel == PowerLevelsConstants.DEFAULT_ROOM_MODERATOR_LEVEL) {
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)
}
roomMemberProfileController.setData(state)
} }
// RoomMemberProfileController.Callback
override fun onIgnoreClicked() {
vectorBaseActivity.notImplemented("Ignore")
}
override fun onLearnMoreClicked() {
vectorBaseActivity.notImplemented("Learn more")
}
override fun onJumpToReadReceiptClicked() {
vectorBaseActivity.notImplemented("Jump to read receipts")
}
override fun onMentionClicked() {
vectorBaseActivity.notImplemented("Mention")
}
} }

View File

@ -22,11 +22,20 @@ 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.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.toModel
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.util.toOptional
import im.vector.matrix.rx.mapOptional
import im.vector.matrix.rx.rx
import im.vector.matrix.rx.unwrap
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import timber.log.Timber import timber.log.Timber
class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted initialState: RoomMemberProfileViewState, class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private val initialState: RoomMemberProfileViewState,
private val session: Session) private val session: Session)
: VectorViewModel<RoomMemberProfileViewState, RoomMemberProfileAction>(initialState) { : VectorViewModel<RoomMemberProfileViewState, RoomMemberProfileAction>(initialState) {
@ -44,8 +53,73 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted initialSt
} }
} }
private val room = if (initialState.roomId != null) {
session.getRoom(initialState.roomId)
} else {
null
}
init {
setState { copy(isMine = session.myUserId == this.userId) }
observeRoomSummary()
observeRoomMemberSummary()
observePowerLevel()
observeUserIfRequired()
}
private fun observeUserIfRequired() {
if (initialState.roomId != null) {
return
}
session.rx().liveUser(initialState.userId)
.unwrap()
.execute {
copy(user = it)
}
}
override fun handle(action: RoomMemberProfileAction) { override fun handle(action: RoomMemberProfileAction) {
Timber.v("Handle $action") Timber.v("Handle $action")
} }
private fun observeRoomSummary() {
if (room == null) {
return
}
room.rx().liveRoomSummary()
.unwrap()
.execute {
copy(roomSummary = it)
}
}
private fun observeRoomMemberSummary() {
if (room == null) {
return
}
val queryParams = roomMemberQueryParams {
this.userId = QueryStringValue.Equals(initialState.userId, QueryStringValue.Case.SENSITIVE)
}
room.rx().liveRoomMembers(queryParams)
.map { it.firstOrNull().toOptional() }
.unwrap()
.execute {
copy(roomMemberSummary = it)
}
}
private fun observePowerLevel() {
if (room == null) {
return
}
room.rx()
.liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS)
.mapOptional { it.content.toModel<PowerLevelsContent>() }
.unwrap()
.execute {
copy(powerLevelsContent = it)
}
}
} }

View File

@ -17,13 +17,35 @@
package im.vector.riotx.features.roommemberprofile package im.vector.riotx.features.roommemberprofile
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
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.user.model.User
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.matrix.android.api.util.toMatrixItem
data class RoomMemberProfileViewState( data class RoomMemberProfileViewState(
val userId: String, val userId: String,
val roomId: String? val roomId: String?,
val isMine: Boolean = false,
val roomSummary: Async<RoomSummary?> = Uninitialized,
val roomMemberSummary: Async<RoomMemberSummary> = Uninitialized,
val user: Async<User> = Uninitialized,
val powerLevelsContent: Async<PowerLevelsContent> = 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 if (roomId == null) {
user.invoke()?.toMatrixItem()
} else {
roomMemberSummary.invoke()?.toMatrixItem()
}
}
} }

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.addFragment import im.vector.riotx.core.extensions.commitTransaction
import im.vector.riotx.core.extensions.addFragmentToBackstack import im.vector.riotx.core.extensions.commitTransactionNow
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,6 +32,9 @@ 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)
@ -50,7 +53,14 @@ 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()) {
addFragment(R.id.simpleFragmentContainer, RoomProfileFragment::class.java, roomProfileArgs) val argsBundle = roomProfileArgs.toMvRxBundle()
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()
@ -73,7 +83,15 @@ class RoomProfileActivity : VectorBaseActivity(), ToolbarConfigurable {
} }
private fun openRoomMembers() { private fun openRoomMembers() {
addFragmentToBackstack(R.id.simpleFragmentContainer, RoomMemberListFragment::class.java, roomProfileArgs) val roomProfileFragment = supportFragmentManager.findFragmentByTag(TAG_ROOM_PROFILE_FRAGMENT)
?: 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

@ -97,6 +97,7 @@ class RoomProfileController @Inject constructor(private val stringProvider: Stri
title = stringProvider.getString(R.string.room_profile_section_more_leave), title = stringProvider.getString(R.string.room_profile_section_more_leave),
divider = false, divider = false,
destructive = true, destructive = true,
editable = false,
action = { callback?.onLeaveRoomClicked() } action = { callback?.onLeaveRoomClicked() }
) )
} }

View File

@ -24,16 +24,16 @@ 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.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
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
import com.google.android.material.appbar.AppBarLayout
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
import im.vector.matrix.android.api.util.toMatrixItem 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.extensions.cleanup
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.VectorBaseFragment import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.AvatarRenderer
@ -42,7 +42,8 @@ import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsBotto
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedAction import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedAction
import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_room_profile.* import kotlinx.android.synthetic.main.fragment_matrix_profile.*
import kotlinx.android.synthetic.main.view_stub_room_profile_header.*
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -63,26 +64,22 @@ class RoomProfileFragment @Inject constructor(
private lateinit var roomProfileSharedActionViewModel: RoomProfileSharedActionViewModel private lateinit var roomProfileSharedActionViewModel: RoomProfileSharedActionViewModel
private val roomProfileViewModel: RoomProfileViewModel by fragmentViewModel() private val roomProfileViewModel: RoomProfileViewModel by fragmentViewModel()
override fun getLayoutResId() = R.layout.fragment_room_profile private lateinit var appBarStateChangeListener: AppBarStateChangeListener
override fun getLayoutResId() = R.layout.fragment_matrix_profile
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
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)
setupToolbar(roomProfileToolbar) matrixProfileHeaderView.apply {
layoutResource = R.layout.view_stub_room_profile_header
inflate()
}
setupRecyclerView() setupRecyclerView()
roomProfileAppBarLayout.addOnOffsetChangedListener(object : AppBarStateChangeListener() { appBarStateChangeListener = MatrixItemAppBarStateChangeListener(matrixProfileCollapsingToolbarLayout.scrimAnimationDuration, listOf(matrixProfileToolbarAvatarImageView, matrixProfileToolbarTitleView))
override fun onStateChanged(appBarLayout: AppBarLayout, state: State) { matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener)
val animationDuration = roomProfileCollapsingToolbarLayout.scrimAnimationDuration
if (state == State.COLLAPSED) {
roomProfileToolbarAvatarImageView.animate().alpha(1f).duration = animationDuration + 100
roomProfileToolbarTitleView.animate().alpha(1f).duration = animationDuration + 100
} else {
roomProfileToolbarAvatarImageView.animate().alpha(0f).duration = animationDuration - 100
roomProfileToolbarTitleView.animate().alpha(0f).duration = animationDuration - 100
}
}
})
roomProfileViewModel.viewEvents roomProfileViewModel.viewEvents
.observe() .observe()
.subscribe { .subscribe {
@ -101,6 +98,11 @@ class RoomProfileFragment @Inject constructor(
.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))
@ -135,14 +137,13 @@ class RoomProfileFragment @Inject constructor(
private fun setupRecyclerView() { private fun setupRecyclerView() {
roomProfileController.callback = this roomProfileController.callback = this
roomProfileRecyclerView.setHasFixedSize(true) matrixProfileRecyclerView.configureWith(roomProfileController, hasFixedSize = true)
roomProfileRecyclerView.layoutManager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
roomProfileRecyclerView.adapter = roomProfileController.adapter
} }
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
roomProfileRecyclerView.adapter = null matrixProfileAppBarLayout.removeOnOffsetChangedListener(appBarStateChangeListener)
matrixProfileRecyclerView.cleanup()
} }
override fun invalidate() = withState(roomProfileViewModel) { state -> override fun invalidate() = withState(roomProfileViewModel) { state ->
@ -152,12 +153,12 @@ class RoomProfileFragment @Inject constructor(
activity?.finish() activity?.finish()
} else { } else {
roomProfileNameView.text = it.displayName roomProfileNameView.text = it.displayName
roomProfileToolbarTitleView.text = it.displayName matrixProfileToolbarTitleView.text = it.displayName
roomProfileAliasView.setTextOrHide(it.canonicalAlias) roomProfileAliasView.setTextOrHide(it.canonicalAlias)
roomProfileTopicView.setTextOrHide(it.topic) roomProfileTopicView.setTextOrHide(it.topic)
val matrixItem = it.toMatrixItem() val matrixItem = it.toMatrixItem()
avatarRenderer.render(matrixItem, roomProfileAvatarView) avatarRenderer.render(matrixItem, roomProfileAvatarView)
avatarRenderer.render(matrixItem, roomProfileToolbarAvatarImageView) avatarRenderer.render(matrixItem, matrixProfileToolbarAvatarImageView)
} }
} }
roomProfileController.setData(state) roomProfileController.setData(state)

View File

@ -19,6 +19,7 @@ package im.vector.riotx.features.roomprofile.members
import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.epoxy.TypedEpoxyController
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
import im.vector.matrix.android.api.util.toMatrixItem import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.core.epoxy.dividerItem
import im.vector.riotx.core.epoxy.profiles.buildProfileSection import im.vector.riotx.core.epoxy.profiles.buildProfileSection
import im.vector.riotx.core.epoxy.profiles.profileMatrixItem import im.vector.riotx.core.epoxy.profiles.profileMatrixItem
import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.StringProvider
@ -56,6 +57,10 @@ class RoomMemberListController @Inject constructor(private val avatarRenderer: A
callback?.onRoomMemberClicked(roomMember) callback?.onRoomMemberClicked(roomMember)
} }
} }
dividerItem {
id("divider_${roomMember.userId}")
}
} }
} }
} }

View File

@ -30,7 +30,6 @@ import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.roomprofile.RoomProfileArgs import im.vector.riotx.features.roomprofile.RoomProfileArgs
import kotlinx.android.synthetic.main.fragment_room_member_list.* import kotlinx.android.synthetic.main.fragment_room_member_list.*
import kotlinx.android.synthetic.main.fragment_room_member_list.recyclerView
import javax.inject.Inject import javax.inject.Inject
@ -48,11 +47,15 @@ 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)
setupToolbar(roomMemberListToolbar)
roomMemberListController.callback = this roomMemberListController.callback = this
recyclerView.configureWith(roomMemberListController, hasFixedSize = true) recyclerView.configureWith(roomMemberListController, hasFixedSize = true)
} }
override fun onResume() {
super.onResume()
setupToolbar(roomMemberListToolbar)
}
override fun onDestroyView() { override fun onDestroyView() {
recyclerView.cleanup() recyclerView.cleanup()
super.onDestroyView() super.onDestroyView()

View File

@ -7,82 +7,33 @@
android:background="?riotx_header_panel_background"> android:background="?riotx_header_panel_background">
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/roomProfileAppBarLayout" android:id="@+id/matrixProfileAppBarLayout"
style="@style/VectorAppBarLayoutStyle" style="@style/VectorAppBarLayoutStyle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<com.google.android.material.appbar.CollapsingToolbarLayout <com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/roomProfileCollapsingToolbarLayout" android:id="@+id/matrixProfileCollapsingToolbarLayout"
style="@style/VectorAppBarLayoutStyle" style="@style/VectorAppBarLayoutStyle"
android:layout_width="match_parent" android:layout_width="match_parent"
app:contentScrim="?riotx_background"
app:scrimAnimationDuration="250"
android:layout_height="match_parent" android:layout_height="match_parent"
app:contentScrim="?riotx_background"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap" app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
app:scrimAnimationDuration="250"
app:titleEnabled="false" app:titleEnabled="false"
app:toolbarId="@+id/roomProfileToolbar"> app:toolbarId="@+id/matrixProfileToolbar">
<LinearLayout <ViewStub
android:id="@+id/roomProfileHeaderView" 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:background="?riotx_background" android:background="?riotx_background"
android:orientation="vertical" android:orientation="vertical"
app:layout_collapseMode="parallax" app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.9"> app:layout_collapseParallaxMultiplier="0.9" />
<ImageView
android:id="@+id/roomProfileAvatarView"
android:layout_width="128dp"
android:layout_height="128dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
tools:src="@tools:sample/avatars" />
<TextView
android:id="@+id/roomProfileNameView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center_vertical"
android:singleLine="true"
android:textAppearance="@style/Vector.Toolbar.Title"
android:textSize="20sp"
android:textStyle="bold"
tools:text="Random" />
<TextView
android:id="@+id/roomProfileAliasView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"
android:singleLine="true"
android:layout_marginBottom="16dp"
android:textAppearance="@style/Vector.Toolbar.Title"
android:textSize="16sp"
tools:text="#random:matrix.org" />
<TextView
android:id="@+id/roomProfileTopicView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginStart="40dp"
android:layout_marginBottom="16dp"
android:layout_marginEnd="40dp"
android:fontFamily="sans-serif"
android:gravity="center"
android:textSize="14sp"
android:textStyle="normal"
tools:text="Here is a room topic, it can be multi-line but should always be displayed in full 🍱" />
</LinearLayout>
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/roomProfileToolbar" android:id="@+id/matrixProfileToolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
android:background="@android:color/transparent" android:background="@android:color/transparent"
@ -93,29 +44,33 @@
android:layout_height="match_parent"> android:layout_height="match_parent">
<ImageView <ImageView
android:id="@+id/roomProfileToolbarAvatarImageView" android:id="@+id/matrixProfileToolbarAvatarImageView"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="40dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
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:src="@tools:sample/avatars" /> tools:src="@tools:sample/avatars" />
<TextView <TextView
android:id="@+id/roomProfileToolbarTitleView" android:id="@+id/matrixProfileToolbarTitleView"
android:layout_width="0dp" android:layout_width="0dp"
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:ellipsize="end"
android:alpha="0"
tools:alpha="1"
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"
app:layout_constraintStart_toEndOf="@+id/roomProfileToolbarAvatarImageView"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/matrixProfileToolbarAvatarImageView"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="@sample/matrix.json/data/roomName" /> tools:text="@sample/matrix.json/data/roomName" />
@ -130,7 +85,7 @@
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/roomProfileRecyclerView" android:id="@+id/matrixProfileRecyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" app:layout_behavior="@string/appbar_scrolling_view_behavior"

View File

@ -9,6 +9,7 @@
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/roomMemberListToolbar" android:id="@+id/roomMemberListToolbar"
style="@style/VectorToolbarStyle" style="@style/VectorToolbarStyle"
android:elevation="4dp"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="?actionBarSize" android:layout_height="?actionBarSize"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View File

@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
~
-->
<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"
android:id="@+id/rootConstraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="RoomMemberProfileFragment"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/matrixProfileHeaderView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?riotx_background"
android:padding="16dp"
android:orientation="vertical">
<ImageView
android:id="@+id/memberProfileAvatarView"
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_gravity="center_horizontal"
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
android:id="@+id/memberProfileIdView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:singleLine="true"
android:textStyle="bold"
android:textAppearance="@style/Vector.Toolbar.Title"
android:textSize="14sp"
tools:text="@sample/matrix.json/data/mxid" />
<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>

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?riotx_background"
android:padding="16dp"
android:orientation="vertical">
<ImageView
android:id="@+id/roomProfileAvatarView"
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/roomProfileNameView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center_vertical"
android:singleLine="true"
android:textAppearance="@style/Vector.Toolbar.Title"
android:textSize="20sp"
android:textStyle="bold"
tools:text="Random" />
<TextView
android:id="@+id/roomProfileAliasView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:singleLine="true"
android:textAppearance="@style/Vector.Toolbar.Title"
android:textSize="14sp"
android:textStyle="bold"
tools:text="#random:matrix.org" />
<TextView
android:id="@+id/roomProfileTopicView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginStart="40dp"
android:layout_marginEnd="40dp"
android:fontFamily="sans-serif"
android:gravity="center"
android:textSize="14sp"
android:textStyle="normal"
tools:text="Here is a room topic, it can be multi-line but should always be displayed in full 🍱" />
</LinearLayout>

View File

@ -41,4 +41,11 @@
<string name="room_member_power_level_invites">Invites</string> <string name="room_member_power_level_invites">Invites</string>
<string name="room_member_power_level_users">Users</string> <string name="room_member_power_level_users">Users</string>
<string name="room_member_power_level_admin_in">Admin in %1$s</string>
<string name="room_member_power_level_moderator_in">Moderator in %1$s</string>
<string name="room_member_power_level_custom_in">Custom (%1$d) in %2$s</string>
<string name="room_member_jump_to_read_receipt">Jump to read receipt</string>
</resources> </resources>