RoomMemberList : start showing items

This commit is contained in:
ganfra 2020-01-09 15:54:36 +01:00
parent 15639b45cf
commit 289951ea4a
15 changed files with 343 additions and 64 deletions

View file

@ -24,8 +24,8 @@ import io.realm.annotations.PrimaryKey
internal open class RoomMemberEntity(@PrimaryKey var primaryKey: String = "", internal open class RoomMemberEntity(@PrimaryKey var primaryKey: String = "",
@Index var userId: String = "", @Index var userId: String = "",
@Index var roomId: String = "", @Index var roomId: String = "",
var displayName: String = "", var displayName: String? = null,
var avatarUrl: String = "", var avatarUrl: String? = null ,
var reason: String? = null, var reason: String? = null,
var isDirect: Boolean = false var isDirect: Boolean = false
) : RealmObject() { ) : RealmObject() {

View file

@ -27,8 +27,8 @@ internal object RoomMemberEntityFactory {
primaryKey = primaryKey, primaryKey = primaryKey,
userId = userId, userId = userId,
roomId = roomId, roomId = roomId,
displayName = roomMember.displayName ?: "", displayName = roomMember.displayName,
avatarUrl = roomMember.avatarUrl ?: "" avatarUrl = roomMember.avatarUrl
).apply { ).apply {
membership = roomMember.membership membership = roomMember.membership
} }

View file

@ -0,0 +1,53 @@
/*
* 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.epoxy.profiles
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
import im.vector.riotx.core.extensions.setTextOrHide
import im.vector.riotx.features.home.AvatarRenderer
@EpoxyModelClass(layout = R.layout.item_profile_matrix_item)
abstract class ProfileMatrixItem : VectorEpoxyModel<ProfileMatrixItem.Holder>() {
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute lateinit var matrixItem: MatrixItem
@EpoxyAttribute var clickListener: View.OnClickListener? = null
override fun bind(holder: Holder) {
val bestName = matrixItem.getBestName()
val matrixId = matrixItem.id.takeIf { it != bestName }
holder.view.setOnClickListener(clickListener)
holder.titleView.text = bestName
holder.subtitleView.setTextOrHide(matrixId)
avatarRenderer.render(matrixItem, holder.avatarImageView)
}
class Holder : VectorEpoxyHolder() {
val titleView by bind<TextView>(R.id.matrixItemTitle)
val subtitleView by bind<TextView>(R.id.matrixItemSubtitle)
val avatarImageView by bind<ImageView>(R.id.matrixItemAvatar)
}
}

View file

@ -912,7 +912,7 @@ class RoomDetailFragment @Inject constructor(
} }
override fun onAvatarClicked(informationData: MessageInformationData) { override fun onAvatarClicked(informationData: MessageInformationData) {
navigator.openRoomMemberProfile(userId = informationData.senderId, context = requireActivity()) navigator.openRoomMemberProfile(userId = informationData.senderId, roomId = roomDetailArgs.roomId, context = requireActivity())
} }
override fun onMemberNameClicked(informationData: MessageInformationData) { override fun onMemberNameClicked(informationData: MessageInformationData) {

View file

@ -19,6 +19,9 @@ import android.view.View
import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.epoxy.TypedEpoxyController
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.core.epoxy.DividerItem_
import im.vector.riotx.core.epoxy.bottomsheet.BottomSheetRoomPreviewItem
import im.vector.riotx.core.epoxy.bottomsheet.BottomSheetRoomPreviewItem_
import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetActionItem import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetActionItem
import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetRoomPreviewItem import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetRoomPreviewItem
import im.vector.riotx.core.epoxy.dividerItem import im.vector.riotx.core.epoxy.dividerItem
@ -35,10 +38,12 @@ class RoomListQuickActionsEpoxyController @Inject constructor(private val avatar
override fun buildModels(state: RoomListQuickActionsState) { override fun buildModels(state: RoomListQuickActionsState) {
val roomSummary = state.roomSummary() ?: return val roomSummary = state.roomSummary() ?: return
val showAll = state.mode == RoomListActionsArgs.Mode.FULL
if (showAll) {
// Preview // Preview
bottomSheetRoomPreviewItem { bottomSheetRoomPreviewItem {
id("preview") id("room_preview")
avatarRenderer(avatarRenderer) avatarRenderer(avatarRenderer)
matrixItem(roomSummary.toMatrixItem()) matrixItem(roomSummary.toMatrixItem())
settingsClickListener(View.OnClickListener { listener?.didSelectMenuAction(RoomListQuickActionsSharedAction.Settings(roomSummary.roomId)) }) settingsClickListener(View.OnClickListener { listener?.didSelectMenuAction(RoomListQuickActionsSharedAction.Settings(roomSummary.roomId)) })
@ -48,6 +53,7 @@ class RoomListQuickActionsEpoxyController @Inject constructor(private val avatar
dividerItem { dividerItem {
id("notifications_separator") id("notifications_separator")
} }
}
val selectedRoomState = state.roomNotificationState() val selectedRoomState = state.roomNotificationState()
RoomListQuickActionsSharedAction.NotificationsAllNoisy(roomSummary.roomId).toBottomSheetItem(0, selectedRoomState) RoomListQuickActionsSharedAction.NotificationsAllNoisy(roomSummary.roomId).toBottomSheetItem(0, selectedRoomState)
@ -55,8 +61,7 @@ class RoomListQuickActionsEpoxyController @Inject constructor(private val avatar
RoomListQuickActionsSharedAction.NotificationsMentionsOnly(roomSummary.roomId).toBottomSheetItem(2, selectedRoomState) RoomListQuickActionsSharedAction.NotificationsMentionsOnly(roomSummary.roomId).toBottomSheetItem(2, selectedRoomState)
RoomListQuickActionsSharedAction.NotificationsMute(roomSummary.roomId).toBottomSheetItem(3, selectedRoomState) RoomListQuickActionsSharedAction.NotificationsMute(roomSummary.roomId).toBottomSheetItem(3, selectedRoomState)
if (showAll) {
if (state.mode == RoomListActionsArgs.Mode.FULL) {
// Leave // Leave
dividerItem { dividerItem {
id("leave_separator") id("leave_separator")

View file

@ -82,7 +82,7 @@ class DefaultNavigator @Inject constructor(
} }
} }
override fun openRoomMemberProfile(userId: 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)
context.startActivity(RoomMemberProfileActivity.newIntent(context, args)) context.startActivity(RoomMemberProfileActivity.newIntent(context, args))
} }

View file

@ -50,7 +50,7 @@ interface Navigator {
fun openGroupDetail(groupId: String, context: Context, buildTask: Boolean = false) fun openGroupDetail(groupId: String, context: Context, buildTask: Boolean = false)
fun openRoomMemberProfile(userId: String, context: Context, buildTask: Boolean = false) fun openRoomMemberProfile(userId: String, roomId: String?, context: Context, buildTask: Boolean = false)
fun openRoomProfile(context: Context, roomId: String) fun openRoomProfile(context: Context, roomId: String)

View file

@ -68,7 +68,7 @@ class PermalinkHandler @Inject constructor(private val session: Session,
Single.just(true) Single.just(true)
} }
is PermalinkData.UserLink -> { is PermalinkData.UserLink -> {
navigator.openRoomMemberProfile(permalinkData.userId, context, buildTask) navigator.openRoomMemberProfile(userId = permalinkData.userId, roomId = null, context = context, buildTask = buildTask)
Single.just(true) Single.just(true)
} }
is PermalinkData.FallbackLink -> { is PermalinkData.FallbackLink -> {

View file

@ -0,0 +1,49 @@
/*
* Copyright 2019 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.roomprofile.members
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.core.epoxy.profiles.profileItemAction
import im.vector.riotx.core.epoxy.profiles.profileMatrixItem
import im.vector.riotx.features.autocomplete.autocompleteMatrixItem
import im.vector.riotx.features.home.AvatarRenderer
import javax.inject.Inject
class RoomMemberListController @Inject constructor(private val avatarRenderer: AvatarRenderer) : TypedEpoxyController<RoomMemberListViewState>() {
interface Callback {
fun onRoomMemberClicked(roomMember: RoomMember)
}
var callback: Callback? = null
override fun buildModels(data: RoomMemberListViewState?) {
data?.roomMembers?.invoke()?.forEach { roomMember ->
profileMatrixItem {
id(roomMember.userId)
matrixItem(roomMember.toMatrixItem())
avatarRenderer(avatarRenderer)
clickListener { _ ->
callback?.onRoomMemberClicked(roomMember)
}
}
}
}
}

View file

@ -21,33 +21,57 @@ import android.view.View
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 im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.extensions.cleanup
import im.vector.riotx.core.extensions.configureWith
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.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.recyclerView
import javax.inject.Inject import javax.inject.Inject
class RoomMemberListFragment @Inject constructor( class RoomMemberListFragment @Inject constructor(
val viewModelFactory: RoomMemberListViewModel.Factory val viewModelFactory: RoomMemberListViewModel.Factory,
) : VectorBaseFragment() { private val roomMemberListController: RoomMemberListController,
private val avatarRenderer: AvatarRenderer
) : VectorBaseFragment(), RoomMemberListController.Callback {
private val viewModel: RoomMemberListViewModel by fragmentViewModel() private val viewModel: RoomMemberListViewModel by fragmentViewModel()
private val roomProfileArgs: RoomProfileArgs by args() private val roomProfileArgs: RoomProfileArgs by args()
override fun getLayoutResId() = R.layout.fragment_room_member_list override fun getLayoutResId() = R.layout.fragment_room_member_list
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(roomMemberListToolbar)
roomMemberListController.callback = this
recyclerView.configureWith(roomMemberListController, hasFixedSize = true)
} }
override fun onDestroyView() { override fun onDestroyView() {
recyclerView.cleanup()
super.onDestroyView() super.onDestroyView()
// Clear your view, unsubscribe...
} }
override fun invalidate() = withState(viewModel) { _ -> override fun invalidate() = withState(viewModel) { viewState ->
roomMemberListController.setData(viewState)
renderRoomSummary(viewState)
}
override fun onRoomMemberClicked(roomMember: RoomMember) {
navigator.openRoomMemberProfile(roomMember.userId, roomId = roomProfileArgs.roomId, context = requireActivity())
}
private fun renderRoomSummary(state: RoomMemberListViewState) {
state.roomSummary()?.let {
roomMemberListToolbarTitleView.text = it.displayName
avatarRenderer.render(it.toMatrixItem(), roomMemberListToolbarAvatarImageView)
}
} }
} }

View file

@ -21,9 +21,19 @@ 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.room.members.roomMemberQueryParams
import im.vector.matrix.android.api.session.room.model.Membership
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 im.vector.riotx.core.utils.DataSource
import im.vector.riotx.core.utils.PublishDataSource
import im.vector.riotx.features.roomprofile.RoomProfileViewEvents
class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState: RoomMemberListViewState) class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState: RoomMemberListViewState,
private val session: Session)
: VectorViewModel<RoomMemberListViewState, RoomMemberListAction>(initialState) { : VectorViewModel<RoomMemberListViewState, RoomMemberListAction>(initialState) {
@AssistedInject.Factory @AssistedInject.Factory
@ -40,8 +50,35 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
} }
} }
private val room = session.getRoom(initialState.roomId)!!
init {
observeRoomMembers()
observeRoomSummary()
}
private fun observeRoomSummary() {
room.rx().liveRoomSummary()
.unwrap()
.execute { async ->
copy(roomSummary = async)
}
}
private fun observeRoomMembers() {
val queryParams = roomMemberQueryParams {
displayName = QueryStringValue.IsNotEmpty
memberships = Membership.activeMemberships()
}
room.rx()
.liveRoomMembers(queryParams)
.execute {
copy(roomMembers = it)
}
}
override fun handle(action: RoomMemberListAction) { override fun handle(action: RoomMemberListAction) {
//TODO
} }
} }

View file

@ -16,8 +16,19 @@
package im.vector.riotx.features.roomprofile.members package im.vector.riotx.features.roomprofile.members
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.RoomMember
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.riotx.features.roomprofile.RoomProfileArgs
data class RoomMemberListViewState( data class RoomMemberListViewState(
val noValue: Boolean = false val roomId: String,
) : MvRxState val roomSummary: Async<RoomSummary> = Uninitialized,
val roomMembers: Async<List<RoomMember>> = Uninitialized
) : MvRxState {
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
}

View file

@ -1,27 +0,0 @@
/*
* Copyright 2019 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.roomprofile.members
import com.airbnb.epoxy.TypedEpoxyController
class RoomMembersEpoxyController : TypedEpoxyController<RoomMemberListViewState>() {
override fun buildModels(data: RoomMemberListViewState?) {
}
}

View file

@ -1,19 +1,67 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rootConstraintLayout" android:id="@+id/rootConstraintLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<TextView <androidx.appcompat.widget.Toolbar
android:id="@+id/message" android:id="@+id/roomMemberListToolbar"
android:layout_width="wrap_content" style="@style/VectorToolbarStyle"
android:layout_height="wrap_content" android:layout_width="0dp"
android:text="RoomMemberListFragment" android:layout_height="?actionBarSize"
app:layout_constraintBottom_toBottomOf="parent" android:elevation="4dp"
android:transitionName="toolbar"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/roomMemberListToolbarContentView"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/roomMemberListToolbarAvatarImageView"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />
<TextView
android:id="@+id/roomMemberListToolbarTitleView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?vctr_toolbar_primary_text_color"
android:textSize="18sp"
app:layout_constraintStart_toEndOf="@+id/roomMemberListToolbarAvatarImageView"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="@sample/matrix.json/data/roomName" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.appcompat.widget.Toolbar>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:overScrollMode="always"
app:layout_constraintTop_toBottomOf="@+id/roomMemberListToolbar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
tools:listitem="@layout/item_autocomplete_matrix_item" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<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:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?riotx_background"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
android:minHeight="64dp"
android:paddingLeft="@dimen/layout_horizontal_margin"
android:paddingTop="8dp"
android:paddingRight="@dimen/layout_horizontal_margin"
android:paddingBottom="8dp">
<ImageView
android:id="@+id/matrixItemAvatar"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_centerVertical="true"
android:scaleType="centerInside"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />
<TextView
android:id="@+id/matrixItemTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:drawablePadding="16dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?riotx_text_primary"
android:textSize="16sp"
app:layout_constraintBottom_toTopOf="@+id/matrixItemSubtitle"
app:layout_constraintEnd_toStartOf="@+id/matrixItemEditable"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/matrixItemAvatar"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_default="wrap"
app:layout_goneMarginStart="0dp"
tools:text="@sample/matrix.json/data/displayName" />
<TextView
android:id="@+id/matrixItemSubtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:drawablePadding="16dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?riotx_text_secondary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/matrixItemEditable"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/matrixItemAvatar"
app:layout_constraintTop_toBottomOf="@id/matrixItemTitle"
app:layout_constraintWidth_default="wrap"
app:layout_goneMarginStart="0dp"
tools:text="@sample/matrix.json/data/mxid" />
<ImageView
android:id="@+id/matrixItemEditable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_arrow_right"
android:tint="?riotx_text_secondary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>