diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomMemberEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomMemberEntity.kt index c532857fe1..d6e41f7705 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomMemberEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomMemberEntity.kt @@ -24,8 +24,8 @@ import io.realm.annotations.PrimaryKey internal open class RoomMemberEntity(@PrimaryKey var primaryKey: String = "", @Index var userId: String = "", @Index var roomId: String = "", - var displayName: String = "", - var avatarUrl: String = "", + var displayName: String? = null, + var avatarUrl: String? = null , var reason: String? = null, var isDirect: Boolean = false ) : RealmObject() { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEntityFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEntityFactory.kt index 51df244401..bc970668bf 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEntityFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberEntityFactory.kt @@ -27,8 +27,8 @@ internal object RoomMemberEntityFactory { primaryKey = primaryKey, userId = userId, roomId = roomId, - displayName = roomMember.displayName ?: "", - avatarUrl = roomMember.avatarUrl ?: "" + displayName = roomMember.displayName, + avatarUrl = roomMember.avatarUrl ).apply { membership = roomMember.membership } diff --git a/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileMatrixItem.kt b/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileMatrixItem.kt new file mode 100644 index 0000000000..4fe65748ce --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileMatrixItem.kt @@ -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() { + + @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(R.id.matrixItemTitle) + val subtitleView by bind(R.id.matrixItemSubtitle) + val avatarImageView by bind(R.id.matrixItemAvatar) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 4998cad700..ee203b5b72 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -912,7 +912,7 @@ class RoomDetailFragment @Inject constructor( } 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) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsEpoxyController.kt index 05a93848b3..0cc409d53d 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsEpoxyController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsEpoxyController.kt @@ -19,6 +19,9 @@ import android.view.View import com.airbnb.epoxy.TypedEpoxyController import im.vector.matrix.android.api.session.room.notification.RoomNotificationState 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.bottomSheetRoomPreviewItem import im.vector.riotx.core.epoxy.dividerItem @@ -35,18 +38,21 @@ class RoomListQuickActionsEpoxyController @Inject constructor(private val avatar override fun buildModels(state: RoomListQuickActionsState) { val roomSummary = state.roomSummary() ?: return + val showAll = state.mode == RoomListActionsArgs.Mode.FULL - // Preview - bottomSheetRoomPreviewItem { - id("preview") - avatarRenderer(avatarRenderer) - matrixItem(roomSummary.toMatrixItem()) - settingsClickListener(View.OnClickListener { listener?.didSelectMenuAction(RoomListQuickActionsSharedAction.Settings(roomSummary.roomId)) }) - } + if (showAll) { + // Preview + bottomSheetRoomPreviewItem { + id("room_preview") + avatarRenderer(avatarRenderer) + matrixItem(roomSummary.toMatrixItem()) + settingsClickListener(View.OnClickListener { listener?.didSelectMenuAction(RoomListQuickActionsSharedAction.Settings(roomSummary.roomId)) }) + } - // Notifications - dividerItem { - id("notifications_separator") + // Notifications + dividerItem { + id("notifications_separator") + } } val selectedRoomState = state.roomNotificationState() @@ -55,8 +61,7 @@ class RoomListQuickActionsEpoxyController @Inject constructor(private val avatar RoomListQuickActionsSharedAction.NotificationsMentionsOnly(roomSummary.roomId).toBottomSheetItem(2, selectedRoomState) RoomListQuickActionsSharedAction.NotificationsMute(roomSummary.roomId).toBottomSheetItem(3, selectedRoomState) - - if (state.mode == RoomListActionsArgs.Mode.FULL) { + if (showAll) { // Leave dividerItem { id("leave_separator") diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt index 76925d9af8..df9a142a9f 100644 --- a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt @@ -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) context.startActivity(RoomMemberProfileActivity.newIntent(context, args)) } diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt index 0e093910f0..4049c2e2c5 100644 --- a/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt @@ -50,7 +50,7 @@ interface Navigator { 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) diff --git a/vector/src/main/java/im/vector/riotx/features/permalink/PermalinkHandler.kt b/vector/src/main/java/im/vector/riotx/features/permalink/PermalinkHandler.kt index 8386a8e45d..6b5f28860d 100644 --- a/vector/src/main/java/im/vector/riotx/features/permalink/PermalinkHandler.kt +++ b/vector/src/main/java/im/vector/riotx/features/permalink/PermalinkHandler.kt @@ -68,7 +68,7 @@ class PermalinkHandler @Inject constructor(private val session: Session, Single.just(true) } is PermalinkData.UserLink -> { - navigator.openRoomMemberProfile(permalinkData.userId, context, buildTask) + navigator.openRoomMemberProfile(userId = permalinkData.userId, roomId = null, context = context, buildTask = buildTask) Single.just(true) } is PermalinkData.FallbackLink -> { diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListController.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListController.kt new file mode 100644 index 0000000000..f9a9b1537d --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListController.kt @@ -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() { + + 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) + } + } + } + } + +} diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListFragment.kt index ae0e990c4a..12dec3783a 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListFragment.kt @@ -21,33 +21,57 @@ import android.view.View import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel 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.core.extensions.cleanup +import im.vector.riotx.core.extensions.configureWith import im.vector.riotx.core.platform.VectorBaseFragment +import im.vector.riotx.features.home.AvatarRenderer 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 class RoomMemberListFragment @Inject constructor( - val viewModelFactory: RoomMemberListViewModel.Factory -) : VectorBaseFragment() { + val viewModelFactory: RoomMemberListViewModel.Factory, + private val roomMemberListController: RoomMemberListController, + private val avatarRenderer: AvatarRenderer +) : VectorBaseFragment(), RoomMemberListController.Callback { private val viewModel: RoomMemberListViewModel by fragmentViewModel() private val roomProfileArgs: RoomProfileArgs by args() + override fun getLayoutResId() = R.layout.fragment_room_member_list override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - // Initialize your view, subscribe to viewModel... + setupToolbar(roomMemberListToolbar) + roomMemberListController.callback = this + recyclerView.configureWith(roomMemberListController, hasFixedSize = true) } override fun onDestroyView() { + recyclerView.cleanup() 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) + } } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt index d1a4592965..38cb09a099 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt @@ -21,9 +21,19 @@ import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted 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.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(initialState) { @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) { - //TODO + } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewState.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewState.kt index 2e432c59f5..223d2c5af6 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewState.kt @@ -16,8 +16,19 @@ package im.vector.riotx.features.roomprofile.members +import com.airbnb.mvrx.Async 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( - val noValue: Boolean = false -) : MvRxState + val roomId: String, + val roomSummary: Async = Uninitialized, + val roomMembers: Async> = Uninitialized +) : MvRxState { + + constructor(args: RoomProfileArgs) : this(roomId = args.roomId) + +} diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMembersEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMembersEpoxyController.kt deleted file mode 100644 index 9fb6c400c3..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMembersEpoxyController.kt +++ /dev/null @@ -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() { - - override fun buildModels(data: RoomMemberListViewState?) { - - } - -} diff --git a/vector/src/main/res/layout/fragment_room_member_list.xml b/vector/src/main/res/layout/fragment_room_member_list.xml index eb453ac2e8..ec38848f32 100644 --- a/vector/src/main/res/layout/fragment_room_member_list.xml +++ b/vector/src/main/res/layout/fragment_room_member_list.xml @@ -1,19 +1,67 @@ - - + app:layout_constraintTop_toTopOf="parent"> + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/item_profile_matrix_item.xml b/vector/src/main/res/layout/item_profile_matrix_item.xml new file mode 100644 index 0000000000..aa5d9e44cf --- /dev/null +++ b/vector/src/main/res/layout/item_profile_matrix_item.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + +